Skip to content

Observation cross section files

Observation cross section files come in two flavours:

Observation cross section .ini files

The obscrosssection module provides the specific logic for accessing observation cross section .ini files. for a D-Flow FM model.

Generic parsing and serializing functionality comes from the generic hydrolib.core.dflowfm.ini modules.

An observation cross section .ini file is described by the classes below.

Model

ObservationCrossSection

Bases: INIBasedModel

The observation cross section that is included in the observation cross section file.

All lowercased attributes match with the observation cross section output as described in [UM Sec.F2.4.1] (https://content.oss.deltares.nl/delft3dfm1d2d/D-Flow_FM_User_Manual_1D2D.pdf#subsubsection.F.2.4.1)

Source code in hydrolib/core/dflowfm/obscrosssection/models.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class ObservationCrossSection(INIBasedModel):
    """
    The observation cross section that is included in the
    observation cross section file.

    All lowercased attributes match with the observation cross
    section output as described in [UM Sec.F2.4.1]
    (https://content.oss.deltares.nl/delft3dfm1d2d/D-Flow_FM_User_Manual_1D2D.pdf#subsubsection.F.2.4.1)
    """

    class Comments(INIBasedModel.Comments):
        name: Optional[str] = "Name of the cross section (max. 255 characters)."
        branchid: Optional[str] = Field(
            "(optional) Branch on which the cross section is located.", alias="branchId"
        )
        chainage: Optional[str] = "(optional) Location on the branch (m)."
        numcoordinates: Optional[str] = Field(
            "(optional) Number of values in xCoordinates and yCoordinates. "
            "This value should be greater than or equal to 2.",
            alias="numCoordinates",
        )
        xcoordinates: Optional[str] = Field(
            "(optional) x-coordinates of the cross section line. "
            "(number of values = numCoordinates)",
            alias="xCoordinates",
        )
        ycoordinates: Optional[str] = Field(
            "(optional) y-coordinates of the cross section line. "
            "(number of values = numCoordinates)",
            alias="yCoordinates",
        )

    comments: Comments = Comments()
    _header: Literal["ObservationCrossSection"] = "ObservationCrossSection"
    name: str = Field(max_length=255, alias="name")
    branchid: Optional[str] = Field(alias="branchId")
    chainage: Optional[float] = Field(alias="chainage")
    numcoordinates: Optional[int] = Field(alias="numCoordinates")
    xcoordinates: Optional[List[float]] = Field(alias="xCoordinates")
    ycoordinates: Optional[List[float]] = Field(alias="yCoordinates")

    _split_to_list = get_split_string_on_delimiter_validator(
        "xcoordinates", "ycoordinates"
    )

    @root_validator(allow_reuse=True)
    def validate_that_location_specification_is_correct(cls, values: Dict) -> Dict:
        """Validates that the correct location specification is given."""
        return validate_location_specification(
            values,
            config=LocationValidationConfiguration(
                validate_node=False,
                minimum_num_coordinates=2,
                validate_location_type=False,
            ),
        )

    def _get_identifier(self, data: dict) -> Optional[str]:
        return data.get("name")

validate_that_location_specification_is_correct(values)

Validates that the correct location specification is given.

Source code in hydrolib/core/dflowfm/obscrosssection/models.py
75
76
77
78
79
80
81
82
83
84
85
@root_validator(allow_reuse=True)
def validate_that_location_specification_is_correct(cls, values: Dict) -> Dict:
    """Validates that the correct location specification is given."""
    return validate_location_specification(
        values,
        config=LocationValidationConfiguration(
            validate_node=False,
            minimum_num_coordinates=2,
            validate_location_type=False,
        ),
    )

ObservationCrossSectionGeneral

Bases: INIGeneral

The observation cross section file's [General] section with file meta data.

Source code in hydrolib/core/dflowfm/obscrosssection/models.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ObservationCrossSectionGeneral(INIGeneral):
    """The observation cross section file's `[General]` section with file meta data."""

    class Comments(INIBasedModel.Comments):
        fileversion: Optional[str] = Field(
            "File version. Do not edit this.", alias="fileVersion"
        )
        filetype: Optional[str] = Field(
            "File type. Should be 'obsCross'. Do not edit this.", alias="fileType"
        )

    comments: Comments = Comments()
    fileversion: str = Field("2.00", alias="fileVersion")
    filetype: Literal["obsCross"] = Field("obsCross", alias="fileType")

ObservationCrossSectionModel

Bases: INIModel

The overall observation cross section model that contains the contents of one observation cross section file.

Source code in hydrolib/core/dflowfm/obscrosssection/models.py
91
92
93
94
95
96
97
98
class ObservationCrossSectionModel(INIModel):
    """
    The overall observation cross section model that contains the contents
    of one observation cross section file.
    """

    general: ObservationCrossSectionGeneral = ObservationCrossSectionGeneral()
    observationcrosssection: List[ObservationCrossSection] = []

Legacy observation cross section .pli files

Legacy .pli files for observation points are supported via the generic polyfile module.

A polyfile (hence also an observation cross section .pli file) is described by the classes below.

Model

models.py defines all classes and functions related to representing pol/pli(z) files.

Description

Bases: BaseModel

Description of a single PolyObject.

The Description will be prepended to a block. Each line will start with a '*'.

Attributes:

Name Type Description
content str

The content of this Description.

Source code in hydrolib/core/dflowfm/polyfile/models.py
11
12
13
14
15
16
17
18
19
20
21
class Description(BaseModel):
    """Description of a single PolyObject.

    The Description will be prepended to a block. Each line will
    start with a '*'.

    Attributes:
        content (str): The content of this Description.
    """

    content: str

Metadata

Bases: BaseModel

Metadata of a single PolyObject.

Attributes:

Name Type Description
name str

The name of the PolyObject

n_rows int

The number of rows (i.e. Point instances) of the PolyObject

n_columns int

The total number of values in a Point, including x, y, and z.

Source code in hydrolib/core/dflowfm/polyfile/models.py
24
25
26
27
28
29
30
31
32
33
34
35
class Metadata(BaseModel):
    """Metadata of a single PolyObject.

    Attributes:
        name (str): The name of the PolyObject
        n_rows (int): The number of rows (i.e. Point instances) of the PolyObject
        n_columns (int): The total number of values in a Point, including x, y, and z.
    """

    name: str
    n_rows: int
    n_columns: int

Point

Bases: BaseModel

Point consisting of a x and y coordinate, an optional z coordinate and data.

Attributes:

Name Type Description
x float

The x-coordinate of this Point

y float

The y-coordinate of this Point

z Optional[float]

An optional z-coordinate of this Point.

data Sequence[float]

The additional data variables of this Point.

Source code in hydrolib/core/dflowfm/polyfile/models.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class Point(BaseModel):
    """Point consisting of a x and y coordinate, an optional z coordinate and data.

    Attributes:
        x (float): The x-coordinate of this Point
        y (float): The y-coordinate of this Point
        z (Optional[float]): An optional z-coordinate of this Point.
        data (Sequence[float]): The additional data variables of this Point.
    """

    x: float
    y: float
    z: Optional[float]
    data: Sequence[float]

    def _get_identifier(self, data: dict) -> Optional[str]:
        x = data.get("x")
        y = data.get("y")
        z = data.get("z")
        return f"x:{x} y:{y} z:{z}"

PolyFile

Bases: ParsableFileModel

Poly-file (.pol/.pli/.pliz) representation.

Notes
  • The has_z_values attribute is used to determine if the PolyFile contains z-values.
  • The has_z_values is false by default and should be set to true if the PolyFile path ends with .pliz.
  • The ***.pliz file should have a 2*3 structure, where the third column contains the z-values, otherwise (the parser will give an error).
  • If there is a label in the file, the parser will ignore the label and read the file as a normal polyline file.
    tfl_01
        2 2
        0.00 1.00 #zee
        0.00 2.00 #zee
    
  • if the file is .pliz, and the dimensions are 2*5 the first three columns will be considered as x, y, z values and the last two columns will be considered as data values.
    L1
        2 5
        63.35 12.95 -4.20 -5.35 0
        45.20 6.35 -3.00 -2.90 0
    
Source code in hydrolib/core/dflowfm/polyfile/models.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
class PolyFile(ParsableFileModel):
    """
    Poly-file (.pol/.pli/.pliz) representation.

    Notes:
        - The `has_z_values` attribute is used to determine if the PolyFile contains z-values.
        - The `has_z_values` is false by default and should be set to true if the PolyFile path ends with `.pliz`.
        - The `***.pliz` file should have a 2*3 structure, where the third column contains the z-values, otherwise
        (the parser will give an error).
        - If there is a label in the file, the parser will ignore the label and read the file as a normal polyline file.
        ```
        tfl_01
            2 2
            0.00 1.00 #zee
            0.00 2.00 #zee
        ```
        - if the file is .pliz, and the dimensions are 2*5 the first three columns will be considered as x, y, z values
        and the last two columns will be considered as data values.
        ```
        L1
            2 5
            63.35 12.95 -4.20 -5.35 0
            45.20 6.35 -3.00 -2.90 0
        ```
    """

    has_z_values: bool = False
    objects: Sequence[PolyObject] = Field(default_factory=list)

    def _serialize(self, _: dict, save_settings: ModelSaveSettings) -> None:
        from .serializer import write_polyfile

        # We skip the passed dict for a better one.
        write_polyfile(self._resolved_filepath, self.objects, self.serializer_config)

    @classmethod
    def _ext(cls) -> str:
        return ".pli"

    @classmethod
    def _filename(cls) -> str:
        return "objects"

    @classmethod
    def _get_serializer(cls) -> Callable:
        # Unused, but requires abstract implementation
        pass

    @classmethod
    def _get_parser(cls) -> Callable:
        # Prevent circular dependency in Parser
        from hydrolib.core.dflowfm.polyfile.parser import read_polyfile

        return read_polyfile

    @property
    def x(self) -> List[float]:
        """X-coordinates of all points in the PolyFile."""
        return [point.x for obj in self.objects for point in obj.points]

    @property
    def y(self) -> List[float]:
        """Y-coordinates of all points in the PolyFile."""
        return [point.y for obj in self.objects for point in obj.points]

    def get_z_sources_sinks(self) -> Tuple[List[float], List[float]]:
        """
        Get the z values of the source and sink points from the polyline file.

        Returns:
            z_source, z_sinkA: Tuple[List[float]]:
            If the polyline has data (more than 3 columns), then both the z_source and z_sink will be a list of two values.
            Otherwise, the z_source and the z_sink will be a single value each.

        Note:
             - calling this method on a polyline file that does not have z-values will return a list of None.

        Examples:
        in case the polyline has 3 columns:
            >>> polyline = PolyFile("tests/data/input/source-sink/leftsor.pliz")
            >>> z_source, z_sink = polyline.get_z_sources_sinks()
            >>> print(z_source, z_sink)
            [-3.0] [-4.2]

        in case the polyline has more than 3 columns:
            >>> polyline = PolyFile("tests/data/input/source-sink/leftsor-5-columns.pliz") #Doctest: +SKIP
            >>> z_source, z_sink = polyline.get_z_sources_sinks()
            >>> print(z_source, z_sink)
            [-3.0, -2.9] [-4.2, -5.35]

        in case the polyline does not have z-values:
            >>> root_dir = "tests/data/input/dflowfm_individual_files/polylines"
            >>> polyline = PolyFile(f"{root_dir}/boundary-polyline-no-z-no-label.pli")
            >>> z_source, z_sink = polyline.get_z_sources_sinks()
            >>> print(z_source, z_sink)
            [None] [None]
        """
        has_data = True if self.objects[0].points[0].data else False

        z_source_sink = []
        for elem in [0, -1]:
            point = self.objects[0].points[elem]
            if has_data:
                z_source_sink.append([point.z, point.data[0]])
            else:
                z_source_sink.append([point.z])

        z_sink: list[float | None] = z_source_sink[0]
        z_source: list[float | None] = z_source_sink[1]
        return z_source, z_sink

    @property
    def number_of_points(self) -> int:
        """Total number of points in the PolyFile."""
        return len(self.x)

number_of_points property

Total number of points in the PolyFile.

x property

X-coordinates of all points in the PolyFile.

y property

Y-coordinates of all points in the PolyFile.

get_z_sources_sinks()

Get the z values of the source and sink points from the polyline file.

Returns:

Type Description
List[float]

z_source, z_sinkA: Tuple[List[float]]:

List[float]

If the polyline has data (more than 3 columns), then both the z_source and z_sink will be a list of two values.

Tuple[List[float], List[float]]

Otherwise, the z_source and the z_sink will be a single value each.

Note
  • calling this method on a polyline file that does not have z-values will return a list of None.

Examples:

in case the polyline has 3 columns: >>> polyline = PolyFile("tests/data/input/source-sink/leftsor.pliz") >>> z_source, z_sink = polyline.get_z_sources_sinks() >>> print(z_source, z_sink) [-3.0][-4.2]

in case the polyline has more than 3 columns

polyline = PolyFile("tests/data/input/source-sink/leftsor-5-columns.pliz") #Doctest: +SKIP z_source, z_sink = polyline.get_z_sources_sinks() print(z_source, z_sink) [-3.0, -2.9][-4.2, -5.35]

in case the polyline does not have z-values

root_dir = "tests/data/input/dflowfm_individual_files/polylines" polyline = PolyFile(f"{root_dir}/boundary-polyline-no-z-no-label.pli") z_source, z_sink = polyline.get_z_sources_sinks() print(z_source, z_sink) [None][]

Source code in hydrolib/core/dflowfm/polyfile/models.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def get_z_sources_sinks(self) -> Tuple[List[float], List[float]]:
    """
    Get the z values of the source and sink points from the polyline file.

    Returns:
        z_source, z_sinkA: Tuple[List[float]]:
        If the polyline has data (more than 3 columns), then both the z_source and z_sink will be a list of two values.
        Otherwise, the z_source and the z_sink will be a single value each.

    Note:
         - calling this method on a polyline file that does not have z-values will return a list of None.

    Examples:
    in case the polyline has 3 columns:
        >>> polyline = PolyFile("tests/data/input/source-sink/leftsor.pliz")
        >>> z_source, z_sink = polyline.get_z_sources_sinks()
        >>> print(z_source, z_sink)
        [-3.0] [-4.2]

    in case the polyline has more than 3 columns:
        >>> polyline = PolyFile("tests/data/input/source-sink/leftsor-5-columns.pliz") #Doctest: +SKIP
        >>> z_source, z_sink = polyline.get_z_sources_sinks()
        >>> print(z_source, z_sink)
        [-3.0, -2.9] [-4.2, -5.35]

    in case the polyline does not have z-values:
        >>> root_dir = "tests/data/input/dflowfm_individual_files/polylines"
        >>> polyline = PolyFile(f"{root_dir}/boundary-polyline-no-z-no-label.pli")
        >>> z_source, z_sink = polyline.get_z_sources_sinks()
        >>> print(z_source, z_sink)
        [None] [None]
    """
    has_data = True if self.objects[0].points[0].data else False

    z_source_sink = []
    for elem in [0, -1]:
        point = self.objects[0].points[elem]
        if has_data:
            z_source_sink.append([point.z, point.data[0]])
        else:
            z_source_sink.append([point.z])

    z_sink: list[float | None] = z_source_sink[0]
    z_source: list[float | None] = z_source_sink[1]
    return z_source, z_sink

PolyObject

Bases: BaseModel

PolyObject describing a single block in a poly file.

The metadata should be consistent with the points: - The number of points should be equal to number of rows defined in the metadata - The data of each point should be equal to the number of columns defined in the metadata.

Attributes:

Name Type Description
description Optional[Description]

An optional description of this PolyObject

metadata Metadata

The Metadata of this PolObject, describing the structure

points List[Point]

The points describing this PolyObject, structured according to the Metadata

Source code in hydrolib/core/dflowfm/polyfile/models.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class PolyObject(BaseModel):
    """PolyObject describing a single block in a poly file.

    The metadata should be consistent with the points:
    - The number of points should be equal to number of rows defined in the metadata
    - The data of each point should be equal to the number of columns defined in the
      metadata.

    Attributes:
        description (Optional[Description]):
            An optional description of this PolyObject
        metadata (Metadata):
            The Metadata of this PolObject, describing the structure
        points (List[Point]):
            The points describing this PolyObject, structured according to the Metadata
    """

    description: Optional[Description]
    metadata: Metadata
    points: List[Point]