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.
Files changed (43) hide show
  1. flood_adapt/__init__.py +1 -1
  2. flood_adapt/adapter/fiat_adapter.py +35 -1
  3. flood_adapt/config/config.py +58 -36
  4. flood_adapt/config/fiat.py +7 -7
  5. flood_adapt/config/gui.py +189 -82
  6. flood_adapt/config/site.py +2 -2
  7. flood_adapt/database_builder/database_builder.py +100 -65
  8. flood_adapt/dbs_classes/database.py +16 -53
  9. flood_adapt/dbs_classes/dbs_event.py +4 -1
  10. flood_adapt/dbs_classes/dbs_measure.py +7 -7
  11. flood_adapt/dbs_classes/dbs_projection.py +4 -1
  12. flood_adapt/dbs_classes/dbs_scenario.py +17 -8
  13. flood_adapt/dbs_classes/dbs_static.py +3 -5
  14. flood_adapt/dbs_classes/dbs_strategy.py +7 -5
  15. flood_adapt/dbs_classes/dbs_template.py +21 -22
  16. flood_adapt/dbs_classes/interface/database.py +0 -8
  17. flood_adapt/dbs_classes/interface/element.py +1 -1
  18. flood_adapt/flood_adapt.py +135 -273
  19. flood_adapt/objects/__init__.py +40 -17
  20. flood_adapt/objects/benefits/benefits.py +6 -6
  21. flood_adapt/objects/events/event_set.py +4 -4
  22. flood_adapt/objects/events/events.py +17 -4
  23. flood_adapt/objects/events/historical.py +11 -8
  24. flood_adapt/objects/events/hurricane.py +11 -8
  25. flood_adapt/objects/events/synthetic.py +9 -7
  26. flood_adapt/objects/forcing/forcing.py +9 -1
  27. flood_adapt/objects/forcing/plotting.py +1 -0
  28. flood_adapt/objects/forcing/tide_gauge.py +14 -14
  29. flood_adapt/objects/forcing/time_frame.py +13 -0
  30. flood_adapt/objects/measures/measures.py +38 -15
  31. flood_adapt/objects/object_model.py +2 -2
  32. flood_adapt/objects/projections/projections.py +18 -18
  33. flood_adapt/objects/strategies/strategies.py +22 -1
  34. flood_adapt/workflows/benefit_runner.py +5 -2
  35. flood_adapt/workflows/scenario_runner.py +8 -7
  36. flood_adapt-0.3.3.dist-info/LICENSE +674 -0
  37. flood_adapt-0.3.3.dist-info/METADATA +859 -0
  38. {flood_adapt-0.3.1.dist-info → flood_adapt-0.3.3.dist-info}/RECORD +41 -41
  39. flood_adapt-0.3.1.dist-info/LICENSE +0 -21
  40. flood_adapt-0.3.1.dist-info/METADATA +0 -183
  41. /flood_adapt/database_builder/templates/{mapbox_layers → output_layers}/bin_colors.toml +0 -0
  42. {flood_adapt-0.3.1.dist-info → flood_adapt-0.3.3.dist-info}/WHEEL +0 -0
  43. {flood_adapt-0.3.1.dist-info → flood_adapt-0.3.3.dist-info}/top_level.txt +0 -0
flood_adapt/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # has to be here at the start to avoid circular imports
2
- __version__ = "0.3.1"
2
+ __version__ = "0.3.3"
3
3
 
4
4
  from flood_adapt import adapter, dbs_classes, objects
5
5
  from flood_adapt.config.config import Settings
@@ -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
- f'"{exe_path.as_posix()}" run settings.toml',
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)
@@ -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
- system_folder: Path = Field(
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
- @computed_field
105
- @property
106
- def sfincs_path(self) -> Path:
107
- return self.system_folder / "sfincs" / f"sfincs{Settings._system_extension()}"
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
- @computed_field
110
- @property
111
- def fiat_path(self) -> Path:
112
- return self.system_folder / "fiat" / f"fiat{Settings._system_extension()}"
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["SYSTEM_FOLDER"] = str(self.system_folder)
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
  """
@@ -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
- benefits: BenefitsModel
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
- from pydantic import BaseModel, Field
5
- from tomli import load as load_toml
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 MapboxLayersModel(BaseModel):
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
- buildings_min_zoom_level : int
17
- The minimum zoom level for the buildings layer.
18
- flood_map_depth_min : float
19
- The minimum depth for the flood map layer.
20
- flood_map_zbmax : float
21
- The maximum depth for the flood map layer.
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
- buildings_min_zoom_level: int = 13
52
- flood_map_depth_min: float
53
- flood_map_zbmax: float
54
- flood_map_bins: list[float]
55
- flood_map_colors: list[str]
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 VisualizationLayersModel(BaseModel):
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
- default_bin_number : int
75
- The default number of bins for the visualization layers.
76
- default_colors : list[str]
77
- The default colors for the visualization layers.
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
- # TODO add check for default_bin_number and default_colors to have the same length
93
- default_bin_number: int
94
- default_colors: list[str]
95
- layer_names: list[str] = Field(default_factory=list)
96
- layer_long_names: list[str] = Field(default_factory=list)
97
- layer_paths: list[str] = Field(default_factory=list)
98
- field_names: list[str] = Field(default_factory=list)
99
- bins: Optional[list[list[float]]] = Field(default_factory=list)
100
- colors: Optional[list[list[str]]] = Field(default_factory=list)
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
- mapbox_layers : MapboxLayersModel
313
+ output_layers : OutputLayers
207
314
  The configuration of the mapbox layers in the GUI.
208
- visualization_layers : VisualizationLayersModel
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
- mapbox_layers: MapboxLayersModel
216
- visualization_layers: VisualizationLayersModel
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 = load_toml(fp)
329
+ toml_contents = tomli.load(fp)
223
330
 
224
331
  return GuiModel(**toml_contents)
@@ -26,8 +26,8 @@ class Site(BaseModel):
26
26
  ----------
27
27
  name : str
28
28
  Name of the site.
29
- description : str, default=""
30
- Description of the site.
29
+ description : str
30
+ Description of the site. Defaults to "".
31
31
  lat : float
32
32
  Latitude of the site.
33
33
  lon : float