Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(object-model): coarse timeseries for imported csv files #672

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions flood_adapt/adapter/sfincs_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
)
from flood_adapt.object_model.hazard.forcing.wind import (
WindConstant,
WindCSV,
WindMeteo,
WindNetCDF,
WindSynthetic,
Expand Down Expand Up @@ -964,6 +965,23 @@ def _add_forcing_wind(
)
ds *= conversion
self._model.setup_wind_forcing_from_grid(wind=ds)
elif isinstance(wind, WindCSV):
df = wind.to_dataframe(time_frame=time_frame)

conversion = us.UnitfulVelocity(
value=1.0, units=wind.units["speed"]
).convert(us.UnitTypesVelocity.mps)
df *= conversion

tmp_path = Path(tempfile.gettempdir()) / "wind.csv"
df.to_csv(tmp_path)

# HydroMT function: set wind forcing from timeseries
self._model.setup_wind_forcing(
timeseries=tmp_path,
magnitude=None,
direction=None,
)
else:
self.logger.warning(
f"Unsupported wind forcing type: {wind.__class__.__name__}"
Expand Down Expand Up @@ -1065,12 +1083,13 @@ def _add_forcing_waterlevels(self, forcing: IWaterlevel):
df_ts *= conversion
self._set_waterlevel_forcing(df_ts)
elif isinstance(forcing, WaterlevelCSV):
df_ts = CSVTimeseries.load_file(path=forcing.path).to_dataframe(
time_frame=time_frame
df_ts = (
CSVTimeseries[forcing.units]
.load_file(path=forcing.path)
.to_dataframe(time_frame=time_frame)
)
if df_ts is None:
raise ValueError("Failed to get waterlevel data.")

conversion = us.UnitfulLength(value=1.0, units=forcing.units).convert(
us.UnitTypesLength.meters
)
Expand Down Expand Up @@ -1264,7 +1283,7 @@ def _set_single_river_forcing(self, discharge: IDischarge):
# Create a geodataframe with the river coordinates, the timeseries data and rename the column to the river index defined in the model
if isinstance(discharge, DischargeCSV):
df = discharge.to_dataframe(time_frame)
conversion = us.UnitfulDischarge(value=1.0, units=discharge.unit).convert(
conversion = us.UnitfulDischarge(value=1.0, units=discharge.units).convert(
us.UnitTypesDischarge.cms
)
elif isinstance(discharge, DischargeConstant):
Expand Down Expand Up @@ -1447,9 +1466,6 @@ def _get_zsmax(self):
self._model.read_results()
zsmax = self._model.results["zsmax"].max(dim="timemax")
zsmax.attrs["units"] = "m"

for name, dataset in self._model.results.items():
dataset.close()
return zsmax

def _get_zs_points(self):
Expand Down
66 changes: 31 additions & 35 deletions flood_adapt/database_builder/create_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,7 @@
SiteModel,
StandardObjectModel,
)
from flood_adapt.object_model.io.unit_system import (
UnitfulDischarge,
UnitfulLength,
UnitTypesLength,
)
from flood_adapt.object_model.io import unit_system as us

config_path = None

Expand Down Expand Up @@ -168,7 +164,7 @@ class TideGaugeConfigModel(BaseModel):

source: TideGaugeSource
file: Optional[str] = None
max_distance: Optional[UnitfulLength] = None
max_distance: Optional[us.UnitfulLength] = None
# TODO add option to add MSL and Datum?
ref: Optional[str] = None

Expand All @@ -194,8 +190,8 @@ class SlrModelDef(SlrModel):
vertical_offset (us.UnitfulLength): The vertical offset of the SLR model, measured in meters.
"""

vertical_offset: UnitfulLength = UnitfulLength(
value=0, units=UnitTypesLength.meters
vertical_offset: us.UnitfulLength = us.UnitfulLength(
value=0, units=us.UnitTypesLength.meters
)


Expand Down Expand Up @@ -986,9 +982,9 @@ def read_sfincs(self):
cstype=self.sfincs.crs.type_name.split(" ")[0].lower(),
offshore_model="offshore" if self.config.sfincs_offshore else None,
overland_model="overland",
floodmap_units="feet"
floodmap_units=us.UnitTypesLength.feet
if self.config.unit_system == UnitSystems.imperial
else "meters",
else us.UnitTypesLength.meters,
save_simulation=False, # for now this defaults to False
)

Expand Down Expand Up @@ -1053,16 +1049,15 @@ def add_rivers(self):
rivers = []

for i, row in river_locs.iterrows():
mean_dis = UnitfulDischarge(
value=self.sfincs.forcing["dis"]
.sel(index=i)
.to_numpy()
.mean(), # in m3/s
units="m3/s",
mean_dis = us.UnitfulDischarge(
value=self.sfincs.forcing["dis"].sel(index=i).to_numpy().mean(),
units=us.UnitTypesDischarge.cms,
)
if self.config.unit_system == "imperial":
mean_dis.value = mean_dis.convert("cfs")
mean_dis.units = "cfs"
if self.config.unit_system == UnitSystems.imperial:
mean_dis = us.UnitfulDischarge(
value=mean_dis.convert(us.UnitTypesDischarge.cfs),
units=us.UnitTypesDischarge.cfs,
)
river = RiverModel(
name=f"river_{i}",
description=f"river_{i}",
Expand Down Expand Up @@ -1129,7 +1124,7 @@ def add_dem(self):
# add site configs
self.site_attrs["sfincs"]["dem"] = DemModel(
filename=fn,
units="meters", # This is always in meters from SFINCS
units=us.UnitTypesLength.meters, # This is always in meters from SFINCS
)

def update_fiat_elevation(self):
Expand All @@ -1147,8 +1142,8 @@ def update_fiat_elevation(self):
self.logger.info(
"Updating FIAT objects ground elevations from SFINCS ground elevation map."
)
SFINCS_units = UnitfulLength(
value=1.0, units="meters"
SFINCS_units = us.UnitfulLength(
value=1.0, units=us.UnitTypesLength.meters
) # SFINCS is always in meters
FIAT_units = self.site_attrs["sfincs"]["config"].floodmap_units
conversion_factor = SFINCS_units.convert(FIAT_units)
Expand Down Expand Up @@ -1268,10 +1263,10 @@ def add_tide_gauge(self):
elv_units = self.site_attrs["sfincs"]["config"].floodmap_units
water_level_config = WaterLevelReferenceModel(
localdatum=VerticalReferenceModel(
name="MSL", height=UnitfulLength(value=0.0, units=elv_units)
name="MSL", height=us.UnitfulLength(value=0.0, units=elv_units)
),
msl=VerticalReferenceModel(
name="MSL", height=UnitfulLength(value=0.0, units=elv_units)
name="MSL", height=us.UnitfulLength(value=0.0, units=elv_units)
),
# other=[]
)
Expand Down Expand Up @@ -1303,22 +1298,23 @@ def add_tide_gauge(self):
ID=int(station["id"]),
lon=station["lon"],
lat=station["lat"],
units=UnitTypesLength.meters,
units=us.UnitTypesLength.meters,
)
water_level_config.msl.height.value = UnitfulLength(
water_level_config.msl.height.value = us.UnitfulLength(
value=station["msl"], units=station["units"]
).convert(elv_units)
water_level_config.localdatum.name = station["datum_name"]
water_level_config.localdatum.height.value = UnitfulLength(
water_level_config.localdatum.height.value = us.UnitfulLength(
value=station["datum"], units=station["units"]
).convert(elv_units)

for name in ["MLLW", "MHHW"]:
val = UnitfulLength(
val = us.UnitfulLength(
value=station[name.lower()], units=station["units"]
).convert(elv_units)
wl_info = VerticalReferenceModel(
name=name, height=UnitfulLength(value=val, units=elv_units)
name=name,
height=us.UnitfulLength(value=val, units=elv_units),
)
water_level_config.other.append(wl_info)

Expand All @@ -1338,7 +1334,7 @@ def add_tide_gauge(self):
description="observations from file stored in database",
source="file",
file=str(Path(file_path.relative_to(self.static_path)).as_posix()),
units=UnitTypesLength.meters,
units=us.UnitTypesLength.meters,
)
self.logger.warning(zero_wl_msg)
# store config
Expand Down Expand Up @@ -1383,15 +1379,15 @@ def _get_closest_station(self, ref: str = "MLLW"):
0,
)
units = self.site_attrs["sfincs"]["config"].floodmap_units
distance = UnitfulLength(value=distance, units="meters")
distance = us.UnitfulLength(value=distance, units=us.UnitTypesLength.meters)
self.logger.info(
f"The closest tide gauge from {self.config.tide_gauge.source} is located {distance.convert(units)} {units} from the SFINCS domain"
)
# Check if user provided max distance
# TODO make sure units are explicit for max_distance
if self.config.tide_gauge.max_distance is not None:
units_new = self.config.tide_gauge.max_distance.units
distance_new = UnitfulLength(
distance_new = us.UnitfulLength(
value=distance.convert(units_new), units=units_new
)
if distance_new.value > self.config.tide_gauge.max_distance.value:
Expand Down Expand Up @@ -1445,7 +1441,7 @@ def add_slr(self):
# TODO better default values

# Make sure units are consistent and make config
vertical_offset = UnitfulLength(
vertical_offset = us.UnitfulLength(
value=self.config.slr.vertical_offset.convert(
self.site_attrs["sfincs"]["config"].floodmap_units
),
Expand Down Expand Up @@ -1515,7 +1511,7 @@ def add_gui_params(self):
self.logger.warning(
"The default tidal amplitude in the GUI will be 0.0, since no tide-gauge water levels are available. You can change this in the site.toml with the 'gui.tide_harmonic_amplitude' attribute."
)
default_tide_harmonic_amplitude = UnitfulLength(
default_tide_harmonic_amplitude = us.UnitfulLength(
value=np.round(amplitude, 3), units=units.default_length_units
)

Expand Down Expand Up @@ -1578,7 +1574,7 @@ def add_general_attrs(self):
) # TODO this could be an input?

self.site_attrs["sfincs"]["flood_frequency"] = FloodFrequencyModel(
flooding_threshold=UnitfulLength(
flooding_threshold=us.UnitfulLength(
value=0.0, units=self.site_attrs["sfincs"]["config"].floodmap_units
) # TODO this could be an input?
)
Expand Down
1 change: 1 addition & 0 deletions flood_adapt/misc/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ def write(self, toml_path: Path) -> None:
with open(toml_path, "wb") as f:
tomli_w.dump(
self.model_dump(
by_alias=True,
exclude={"sfincs_path", "fiat_path", "database_path"},
),
f,
Expand Down
10 changes: 5 additions & 5 deletions flood_adapt/object_model/hazard/event/historical.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,26 @@ class HistoricalEventModel(EventModel):

ALLOWED_FORCINGS: ClassVar[dict[ForcingType, List[ForcingSource]]] = {
ForcingType.RAINFALL: [
ForcingSource.CONSTANT,
ForcingSource.CSV,
ForcingSource.METEO,
ForcingSource.SYNTHETIC,
ForcingSource.CONSTANT,
],
ForcingType.WIND: [
ForcingSource.CONSTANT,
ForcingSource.CSV,
ForcingSource.METEO,
ForcingSource.CONSTANT,
],
ForcingType.WATERLEVEL: [
ForcingSource.CSV,
ForcingSource.MODEL,
ForcingSource.GAUGED,
ForcingSource.CSV,
ForcingSource.SYNTHETIC,
ForcingSource.GAUGED,
],
ForcingType.DISCHARGE: [
ForcingSource.CONSTANT,
ForcingSource.CSV,
ForcingSource.SYNTHETIC,
ForcingSource.CONSTANT,
],
}
template: Template = Template.Historical
Expand Down
2 changes: 1 addition & 1 deletion flood_adapt/object_model/hazard/event/hurricane.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ class HurricaneEventModel(EventModel):
ForcingType.WIND: [ForcingSource.TRACK],
ForcingType.WATERLEVEL: [ForcingSource.MODEL],
ForcingType.DISCHARGE: [
ForcingSource.CONSTANT,
ForcingSource.CSV,
ForcingSource.SYNTHETIC,
ForcingSource.CONSTANT,
],
}
template: Template = Template.Hurricane
Expand Down
17 changes: 13 additions & 4 deletions flood_adapt/object_model/hazard/event/synthetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,23 @@ class SyntheticEventModel(EventModel):
"""BaseModel describing the expected variables and data types for parameters of Synthetic that extend the parent class Event."""

ALLOWED_FORCINGS: ClassVar[dict[ForcingType, List[ForcingSource]]] = {
ForcingType.RAINFALL: [ForcingSource.CONSTANT, ForcingSource.SYNTHETIC],
ForcingType.WIND: [ForcingSource.CONSTANT, ForcingSource.CSV],
ForcingType.WATERLEVEL: [ForcingSource.SYNTHETIC, ForcingSource.CSV],
ForcingType.DISCHARGE: [
ForcingType.RAINFALL: [
ForcingSource.CONSTANT,
ForcingSource.SYNTHETIC,
],
ForcingType.WIND: [
ForcingSource.CSV,
ForcingSource.CONSTANT,
],
ForcingType.WATERLEVEL: [
ForcingSource.CSV,
ForcingSource.SYNTHETIC,
],
ForcingType.DISCHARGE: [
ForcingSource.CSV,
ForcingSource.SYNTHETIC,
ForcingSource.CONSTANT,
],
}
template: Template = Template.Synthetic

Expand Down
10 changes: 6 additions & 4 deletions flood_adapt/object_model/hazard/forcing/discharge.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def to_dataframe(self, time_frame: TimeModel) -> pd.DataFrame:
time = pd.date_range(
start=time_frame.start_time,
end=time_frame.end_time,
freq=TimeModel().time_step,
freq=time_frame.time_step,
name="time",
)
data = [self.discharge.value for _ in range(len(time))]
Expand All @@ -49,11 +49,13 @@ class DischargeCSV(IDischarge):
source: ForcingSource = ForcingSource.CSV

path: Path
unit: us.UnitTypesDischarge = us.UnitTypesDischarge.cms
units: us.UnitTypesDischarge = us.UnitTypesDischarge.cms

def to_dataframe(self, time_frame: TimeModel) -> pd.DataFrame:
return CSVTimeseries.load_file(path=self.path).to_dataframe(
time_frame=time_frame
return (
CSVTimeseries[self.units]
.load_file(path=self.path)
.to_dataframe(time_frame=time_frame)
)

def save_additional(self, output_dir: Path | str | os.PathLike) -> None:
Expand Down
8 changes: 5 additions & 3 deletions flood_adapt/object_model/hazard/forcing/rainfall.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@ class RainfallCSV(IRainfall):
source: ForcingSource = ForcingSource.CSV

path: Path
unit: us.UnitTypesIntensity = us.UnitTypesIntensity.mm_hr
units: us.UnitTypesIntensity = us.UnitTypesIntensity.mm_hr

def to_dataframe(self, time_frame: TimeModel) -> pd.DataFrame:
return CSVTimeseries.load_file(path=self.path).to_dataframe(
time_frame=time_frame
return (
CSVTimeseries[self.units]
.load_file(path=self.path)
.to_dataframe(time_frame=time_frame)
)

def save_additional(self, output_dir: Path | str | os.PathLike) -> None:
Expand Down
Loading
Loading