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

112 improvements of structures #113

Merged
merged 6 commits into from
Nov 15, 2023
Merged
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
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ Unreleased

Added
-----
- Support reading and writing of compound structures. (PR#113)

Changed
-------
- Support multiple structures at the same location. (PR#113)

Fixed
-----
Expand Down
27 changes: 24 additions & 3 deletions examples/dflowfm_local/dflowfm/structures.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ fileType = structure

[Structure]
id = bridge_1 # Unique structure id (max. 256 characters).
name = id # Given name in the user interface.
name = bridge_1 # Given name in the user interface.
type = bridge # Structure type; must read bridge
branchId = branch_12 # Branch on which the structure is located.
chainage = 14.101 # Chainage on the branch (m).
Expand All @@ -21,7 +21,7 @@ length = 10.0 # Length [m], L.

[Structure]
id = bridge_2 # Unique structure id (max. 256 characters).
name = id # Given name in the user interface.
name = bridge_2 # Given name in the user interface.
type = bridge # Structure type; must read bridge
branchId = branch_3 # Branch on which the structure is located.
chainage = 485.794 # Chainage on the branch (m).
Expand All @@ -36,7 +36,7 @@ length = 5.0 # Length [m], L.

[Structure]
id = culvert_1 # Unique structure id (max. 256 characters).
name = id # Given name in the user interface.
name = culvert_1 # Given name in the user interface.
type = culvert
branchId = branch_13 # Branch on which the structure is located.
chainage = 331.832 # Chainage on the branch (m).
Expand All @@ -52,3 +52,24 @@ bedFrictionType = Manning
bedFriction = 0.012
subType = culvert

[Structure]
id = CompoundStructure_1 # Unique structure id (max. 256 characters).
name = CompoundStructure_1 # Given name in the user interface.
type = compound
numStructures = 1
structureIds = bridge_1

[Structure]
id = CompoundStructure_2 # Unique structure id (max. 256 characters).
name = CompoundStructure_2 # Given name in the user interface.
type = compound
numStructures = 1
structureIds = culvert_1

[Structure]
id = CompoundStructure_3 # Unique structure id (max. 256 characters).
name = CompoundStructure_3 # Given name in the user interface.
type = compound
numStructures = 1
structureIds = bridge_2

34 changes: 14 additions & 20 deletions hydromt_delft3dfm/dflowfm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1494,19 +1494,18 @@ def setup_bridges(
bridge_filter: Optional[str] = None,
snap_offset: Optional[float] = None,
):
"""Prepares bridges, including bridge locations and bridge crossections.
"""Prepare bridges, including bridge locations and bridge crossections.

The bridges are read from ``bridges_fn`` and if any missing, filled with information provided in ``bridges_defaults_fn``.

When reading ``bridges_fn``, only locations within the region will be read.
Read locations are then filtered for value specified in ``bridge_filter`` on the column "structure_type" .
Read locations are then filtered for value specified in ``bridge_filter`` on the column "structure_type".
Remaining locations are snapped to the existing network within a max distance defined in ``snap_offset`` and will be dropped if not snapped.

A default rectangle bridge profile can be found in ``bridges_defaults_fn`` as an example.

Structure attributes ['structure_id', 'structure_type'] are either taken from data or generated in the script.
Structure attributes ['shape', 'diameter', 'width', 't_width', 'height', 'closed', 'shift', 'length', 'pillarwidth', 'formfactor', 'friction_type', 'friction_value', 'allowedflowdir', 'inletlosscoeff', 'outletlosscoeff']
are either taken from data, or in case of missing read from defaults.
Structure attributes ['shape', 'diameter', 'width', 't_width', 'height', 'closed', 'shift', 'length', 'pillarwidth', 'formfactor', 'friction_type', 'friction_value', 'allowedflowdir', 'inletlosscoeff', 'outletlosscoeff'] are either taken from data, or in case of missing read from defaults.

Adds/Updates model layers:
* **bridges** geom: 1D bridges vector
Expand Down Expand Up @@ -1535,11 +1534,12 @@ def setup_bridges(
See Also
--------
dflowfm._setup_1dstructures
"""
""" # noqa: E501
snap_offset = self._network_snap_offset if snap_offset is None else snap_offset
_st_type = "bridge"
_allowed_columns = [
"id",
"name",
"type",
"branchid",
"chainage",
Expand Down Expand Up @@ -1583,6 +1583,7 @@ def setup_bridges(
bridges.rename(
columns={
"structure_id": "id",
"structure_name": "name",
"structure_type": "type",
"structure_branchid": "branchid",
"structure_chainage": "chainage",
Expand All @@ -1605,7 +1606,7 @@ def setup_culverts(
culvert_filter: Optional[str] = None,
snap_offset: Optional[float] = None,
):
"""Prepares culverts, including locations and crossections. Note that only subtype culvert is supported, i.e. inverted siphon is not supported.
"""Prepare culverts, including locations and crossections. Note that only subtype culvert is supported, i.e. inverted siphon is not supported.

The culverts are read from ``culverts_fn`` and if any missing, filled with information provided in ``culverts_defaults_fn``.

Expand All @@ -1616,10 +1617,7 @@ def setup_culverts(
A default ``culverts_defaults_fn`` that defines a circle culvert profile can be found in dflowfm.data.culverts as an example.

Structure attributes ['structure_id', 'structure_type'] are either taken from data or generated in the script.
Structure attributes ['shape', 'diameter', 'width', 't_width', 'height', 'closed', 'leftlevel', 'rightlevel', 'length',
'valveonoff', 'valveopeningheight', 'numlosscoeff', 'relopening', 'losscoeff',
'friction_type', 'friction_value', 'allowedflowdir', 'inletlosscoeff', 'outletlosscoeff']
are either taken from data, or in case of missing read from defaults.
Structure attributes ['shape', 'diameter', 'width', 't_width', 'height', 'closed', 'leftlevel', 'rightlevel', 'length','valveonoff', 'valveopeningheight', 'numlosscoeff', 'relopening', 'losscoeff', 'friction_type', 'friction_value', 'allowedflowdir', 'inletlosscoeff', 'outletlosscoeff'] are either taken from data, or in case of missing read from defaults.

Adds/Updates model layers:
* **culverts** geom: 1D culverts vector
Expand All @@ -1630,36 +1628,31 @@ def setup_culverts(
Path or data source name for culverts, see data/data_sources.yml.
Note only the points that are within the region polygon will be used.

* Optional variables: ['structure_id', 'structure_type', 'shape', 'diameter', 'width', 't_width', 'height', 'closed',
'leftlevel', 'rightlevel', 'length', 'valveonoff', 'valveopeningheight',
'numlosscoeff', 'relopening', 'losscoeff',
'friction_type', 'friction_value', 'allowedflowdir', 'inletlosscoeff', 'outletlosscoeff']
* Optional variables: ['structure_id', 'structure_type', 'shape', 'diameter', 'width', 't_width', 'height', 'closed', 'leftlevel', 'rightlevel', 'length', 'valveonoff', 'valveopeningheight', 'numlosscoeff', 'relopening', 'losscoeff', 'friction_type', 'friction_value', 'allowedflowdir', 'inletlosscoeff', 'outletlosscoeff']

culverts_defaults_fn : str Path, optional
Path to a csv file containing all defaults values per "structure_type".
By default `hydrolib.hydromt_delft3dfm.data.culverts.culverts_defaults.csv` is used.
This file describes a default circle culvert profile.

* Allowed variables: ['structure_type', 'shape', 'diameter', 'width', 't_width', 'height', 'closed',
'leftlevel', 'rightlevel', 'length', 'valveonoff', 'valveopeningheight',
'numlosscoeff', 'relopening', 'losscoeff',
'friction_type', 'friction_value', 'allowedflowdir', 'inletlosscoeff', 'outletlosscoeff']
* Allowed variables: ['structure_type', 'shape', 'diameter', 'width', 't_width', 'height', 'closed', 'leftlevel', 'rightlevel', 'length', 'valveonoff', 'valveopeningheight', 'numlosscoeff', 'relopening', 'losscoeff', 'friction_type', 'friction_value', 'allowedflowdir', 'inletlosscoeff', 'outletlosscoeff']

culvert_filter: str, optional
Keyword in "structure_type" column of ``culverts_fn`` used to filter culvert features. If None all features are used (default).

snap_offset: float, optional
Snapping tolenrance to automatically snap culverts to network and add ['branchid', 'chainage'] attributes.
By default None. In this case, global variable "network_snap_offset" will be used..
By default None. In this case, global variable "network_snap_offset" will be used.

See Also
--------
dflowfm._setup_1dstructures
"""
""" # noqa: E501
snap_offset = self._network_snap_offset if snap_offset is None else snap_offset
_st_type = "culvert"
_allowed_columns = [
"id",
"name",
"type",
"branchid",
"chainage",
Expand Down Expand Up @@ -1709,6 +1702,7 @@ def setup_culverts(
culverts.rename(
columns={
"structure_id": "id",
"structure_name": "name",
"structure_type": "type",
"structure_branchid": "branchid",
"structure_chainage": "chainage",
Expand Down
29 changes: 29 additions & 0 deletions hydromt_delft3dfm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,8 @@ def read_structures(branches: gpd.GeoDataFrame, fm_model: FMModel) -> gpd.GeoDat
Read structures into hydrolib-core structures objects
Returns structures geodataframe.

Will drop compound structures.

Parameters
----------
branches: geopandas.GeoDataFrame
Expand Down Expand Up @@ -441,6 +443,9 @@ def read_structures(branches: gpd.GeoDataFrame, fm_model: FMModel) -> gpd.GeoDat
axis=1,
)

# Drop compound structures (only write but do not read it back)
df_structures = df_structures[df_structures["type"] != "compound"]

# Add geometry
gdf_structures = gis_utils.get_gdf_from_branches(branches, df_structures)

Expand All @@ -451,6 +456,8 @@ def write_structures(gdf: gpd.GeoDataFrame, savedir: str) -> str:
"""
write structures into hydrolib-core structures objects.

Will add compound structures.

Parameters
----------
gdf: geopandas.GeoDataFrame
Expand All @@ -463,6 +470,28 @@ def write_structures(gdf: gpd.GeoDataFrame, savedir: str) -> str:
structures_fn: str
relative path to structures file.
"""

# Add compound structures
cmp_structures = gdf.groupby(["chainage", "branchid"])["id"].apply(list)
for cmp_count, cmp_st in enumerate(cmp_structures, start=1):
gdf = pd.concat(
[
gdf,
pd.DataFrame(
index=[max(gdf.index) + 1],
data={
"id": [f"CompoundStructure_{cmp_count}"],
"name": [f"CompoundStructure_{cmp_count}"],
"type": ["compound"],
"numStructures": [len(cmp_st)],
"structureIds": [";".join(cmp_st)],
},
),
],
axis=0,
)

# Write structures
structures = StructureModel(structure=gdf.to_dict("records"))

structures_fn = structures._filename() + ".ini"
Expand Down
11 changes: 6 additions & 5 deletions hydromt_delft3dfm/workflows/crosssections.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ def set_xyz_crosssections(


def set_point_crosssections(
branches: gpd.GeoDataFrame, crosssections: gpd.GeoDataFrame, maxdist: float = 1.0
branches: gpd.GeoDataFrame, crosssections: gpd.GeoDataFrame, maxdist: float = 1.0, check_dupl_geom: bool = True,
):
"""
Set regular cross-sections from point.
Expand Down Expand Up @@ -558,10 +558,11 @@ def set_point_crosssections(
logger.error("mismatch crs between cross-sections and branches")

# remove duplicated geometries
_nodes = crosssections.copy()
G = _nodes["geometry"].apply(lambda geom: geom.wkb)
# check for diff in numbers: n = len(G) - len(G.drop_duplicates().index)
crosssections = _nodes[_nodes.index.isin(G.drop_duplicates().index)]
if check_dupl_geom:
_nodes = crosssections.copy()
G = _nodes["geometry"].apply(lambda geom: geom.wkb)
# check for diff in numbers: n = len(G) - len(G.drop_duplicates().index)
crosssections = _nodes[_nodes.index.isin(G.drop_duplicates().index)]

# snap to branch
# setup branch_id - snap bridges to branch
Expand Down
10 changes: 5 additions & 5 deletions hydromt_delft3dfm/workflows/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def prepare_1dstructures(
gdf_st = update_data_columns_attributes_based_on_filter(
gdf_st, params, type_col, st_type
)

# 4. snap structures to branches
# setup branch_id - snap structures to branch (inplace of structures,
# will add branch_id and branch_offset columns)
Expand Down Expand Up @@ -131,7 +131,7 @@ def prepare_1dstructures(
if "shift" not in gdf_st.columns:
gdf_st["shift"] = np.nan
# derive crosssections
gdf_st_crossections = set_point_crosssections(branches, gdf_st, maxdist=snap_offset)
gdf_st_crossections = set_point_crosssections(branches, gdf_st, maxdist=snap_offset, check_dupl_geom=False)
# remove crossection locations and any friction from the setup
gdf_st_crsdefs = gdf_st_crossections.drop(
columns=[
Expand All @@ -148,7 +148,7 @@ def prepare_1dstructures(
# 6. replace np.nan as None
gdf_st = gdf_st.replace(np.nan, None)

# 7. remove index
gdf_st = gdf_st.reset_index()

# 7. remove index and add name
gdf_st = gdf_st.reset_index(names=id_col) # force colname to index_col with names=
gdf_st["structure_name"] = gdf_st[id_col]
return gdf_st
Loading