from __future__ import annotations
import datetime
from functools import cache, cached_property
from typing import List
from baec.measurements.settlement_rod_measurement import (
SettlementRodMeasurement,
SettlementRodMeasurementStatus,
StatusMessage,
)
from baec.project import Project
[docs]class MeasuredSettlement:
"""
Represents the measured settlement derived from a single settlement rod measurement.
It includes the thickness of the fill layer and the x and y displacements at the rod top.
"""
[docs] def __init__(
self,
project: Project,
object_id: str,
start_date_time: datetime.datetime,
date_time: datetime.datetime,
fill_thickness: float,
settlement: float,
x_displacement: float,
y_displacement: float,
horizontal_units: str,
vertical_units: str,
status: SettlementRodMeasurementStatus,
status_messages: List[StatusMessage],
) -> None:
"""
Initializes a MeasuredSettlement object.
Parameters
----------
project : Project
The project which the measured settlement belongs to.
object_id : str
The ID of the measured object.
start_date_time : datetime.datetime
The date and time of the start of the measurements (zero measurement).
date_time : datetime.datetime
The date and time of the measured settlement.
fill_thickness : float
The thickness of the fill layer.
Units are according to `vertical_units`.
settlement : float
The settlement of the initial ground profile relative to the zero measurement.
A positive (+) settlement value represents a downward movement.
Units are according to `vertical_units`.
x_displacement : float
The horizontal X-displacement relative to the zero measurement.
Units are according to the `horizontal_units`.
y_displacement : float
The horizontal Y-displacement relative to the zero measurement.
Units are according to the `horizontal_units`.
horizontal_units: str
The units of the horizontal XY displacements.
vertical_units: str
The units of the measurements and distances in the vertical direction.
status: SettlementRodMeasurementStatus
The status of the settlement rod measurement from which the measured settlement
is derived.
status_messages: List[StatusMessage]
The list of status messages about the settlement rod measurement from which the
measured settlement is derived.
Raises
------
TypeError
If the input types are incorrect.
ValueError
If empty string for `object_id`, `horizontal_units` and `vertical_units`.
If `date_time` is not >= `start_date_time`.
If negative value for `fill_thickness`.
"""
# Initialize all attributes using private setters.
self._set_project(project)
self._set_object_id(object_id)
self._set_start_date_time(start_date_time)
self._set_date_time(date_time)
self._set_fill_thickness(fill_thickness)
self._set_settlement(settlement)
self._set_x_displacement(x_displacement)
self._set_y_displacement(y_displacement)
self._set_horizontal_units(horizontal_units)
self._set_vertical_units(vertical_units)
self._set_status(status)
self._set_status_messages(status_messages)
[docs] @classmethod
def from_settlement_rod_measurement(
cls,
measurement: SettlementRodMeasurement,
zero_measurement: SettlementRodMeasurement,
) -> MeasuredSettlement:
"""
Create a MeasuredSettlement object from a measurement and a zero measurement.
Parameters
----------
measurement : SettlementRodMeasurement
The measurement to be interpreted.
zero_measurement : SettlementRodMeasurement
The measurement considered to the the zero measurement.
Returns
-------
MeasuredSettlement
The derived MeasuredSettlement object relative to the zero measurement.
Raises
------
TypeError
If the types are incorrect.
ValueError
If the measurements do not belong to the same project, object_id or coordinate
reference systems.
"""
# Check types are correct
if not isinstance(measurement, SettlementRodMeasurement):
raise TypeError(
"Expected 'SettlementRodMeasurement' type for 'measurement'."
)
if not isinstance(zero_measurement, SettlementRodMeasurement):
raise TypeError(
"Expected 'SettlementRodMeasurement' type for 'zero_measurement'."
)
# Check if both measurements belong to the same project, object and coordinate
# reference systems.
if measurement.project != zero_measurement.project:
raise ValueError("Both measurements must belong to the same project.")
if measurement.object_id != zero_measurement.object_id:
raise ValueError("Both measurements must belong to the same object.")
if (
measurement.coordinate_reference_systems
!= zero_measurement.coordinate_reference_systems
):
raise ValueError(
"Both measurements must have the same coordinate reference systems."
)
return cls(
project=measurement.project,
object_id=measurement.object_id,
start_date_time=zero_measurement.date_time,
date_time=measurement.date_time,
fill_thickness=measurement.ground_surface_z - measurement.rod_bottom_z,
settlement=zero_measurement.rod_bottom_z - measurement.rod_bottom_z,
x_displacement=measurement.rod_top_x - zero_measurement.rod_top_x,
y_displacement=measurement.rod_top_y - zero_measurement.rod_top_y,
horizontal_units=measurement.coordinate_reference_systems.horizontal_units,
vertical_units=measurement.coordinate_reference_systems.vertical_units,
status=measurement.status,
status_messages=measurement.status_messages,
)
def _set_project(self, value: Project) -> None:
"""
Private setter for project attribute.
"""
if not isinstance(value, Project):
raise TypeError("Expected 'Project' type for 'project' attribute.")
self._project = value
def _set_object_id(self, value: str) -> None:
"""
Private setter for object_id attribute.
"""
if not isinstance(value, str):
raise TypeError("Expected 'str' type for 'object_id' attribute.")
if value == "":
raise ValueError("Empty string not allowed for 'object_id' attribute.")
self._object_id = value
def _set_start_date_time(self, value: datetime.datetime) -> None:
"""
Private setter for start_date_time attribute.
"""
if not isinstance(value, datetime.datetime):
raise TypeError(
"Expected 'datetime.datetime' type for 'start_date_time' attribute."
)
self._start_date_time = value
def _set_date_time(self, value: datetime.datetime) -> None:
"""
Private setter for date_time attribute.
"""
if not isinstance(value, datetime.datetime):
raise TypeError(
"Expected 'datetime.datetime' type for 'date_time' attribute."
)
if not value >= self.start_date_time:
raise ValueError("Value of 'date_time' must be >= 'start_date_time'.")
self._date_time = value
def _set_fill_thickness(self, value: float) -> None:
"""
Private setter for fill_thickness attribute.
"""
if isinstance(value, int):
value = float(value)
if not isinstance(value, float):
raise TypeError("Expected 'float' type for 'fill_thickness' attribute.")
if value < 0:
raise ValueError(
"Negative value not allowed for 'fill_thickness' attribute."
)
self._fill_thickness = value
def _set_settlement(self, value: float) -> None:
"""
Private setter for settlement attribute.
"""
if isinstance(value, int):
value = float(value)
if not isinstance(value, float):
raise TypeError("Expected 'float' type for 'settlement' attribute.")
self._settlement = value
def _set_x_displacement(self, value: float) -> None:
"""
Private setter for x_displacement attribute.
"""
if isinstance(value, int):
value = float(value)
if not isinstance(value, float):
raise TypeError("Expected 'float' type for 'x_displacement' attribute.")
self._x_displacement = value
def _set_y_displacement(self, value: float) -> None:
"""
Private setter for y_displacement attribute.
"""
if isinstance(value, int):
value = float(value)
if not isinstance(value, float):
raise TypeError("Expected 'float' type for 'y_displacement' attribute.")
self._y_displacement = value
def _set_horizontal_units(self, value: str) -> None:
"""
Private setter for horizontal_units attribute.
"""
if not isinstance(value, str):
raise TypeError("Expected 'str' type for 'horizontal_units' attribute.")
if value == "":
raise ValueError(
"Empty string not allowed for 'horizontal_units' attribute."
)
self._horizontal_units = value
def _set_vertical_units(self, value: str) -> None:
"""
Private setter for vertical_units attribute.
"""
if not isinstance(value, str):
raise TypeError("Expected 'str' type for 'vertical_units' attribute.")
if value == "":
raise ValueError("Empty string not allowed for 'vertical_units' attribute.")
self._vertical_units = value
def _set_status(self, value: SettlementRodMeasurementStatus) -> None:
"""
Private setter for status attribute.
"""
if not isinstance(value, SettlementRodMeasurementStatus):
raise TypeError(
"Expected 'SettlementRodMeasurementStatus' type for 'status' attribute."
)
self._status = value
def _set_status_messages(self, value: List[StatusMessage]) -> None:
"""
Private setter for status attribute.
"""
if not isinstance(value, list):
raise TypeError(
"Expected 'List[StatusMessage]' type for 'status_messages' attribute."
)
# Check if the input is a list of StatusMessage objects.
if not all(isinstance(item, StatusMessage) for item in value):
raise TypeError(
"Expected 'List[StatusMessage]' type for 'status_messages' attribute."
)
self._status_messages = value
@property
def project(self) -> Project:
"""
The project which the measured settlement belongs to.
"""
return self._project
@property
def object_id(self) -> str:
"""
The ID of the measured object.
"""
return self._object_id
@property
def start_date_time(self) -> datetime.datetime:
"""
The date and time of the start of the measurements (zero measurement).
"""
return self._start_date_time
@property
def date_time(self) -> datetime.datetime:
"""
The date and time of the measured settlement.
"""
return self._date_time
@cached_property
def days(self) -> float:
"""
The time elapsed since the zero measurement in [days].
"""
return (self.date_time - self.start_date_time).total_seconds() / 86400.0
@property
def fill_thickness(self) -> float:
"""
The thickness of the fill layer.
Units are according to `vertical_units`.
"""
return self._fill_thickness
@property
def settlement(self) -> float:
"""
The settlement of the initial ground profile relative to the zero measurement.
A positive (+) settlement value represents a downward movement.
Units are according to `vertical_units`.
"""
return self._settlement
@property
def x_displacement(self) -> float:
"""
The horizontal X-displacement at the rod top relative to the zero measurement.
Units are according to the `horizontal_units`.
"""
return self._x_displacement
@property
def y_displacement(self) -> float:
"""
The horizontal Y-displacement at the rod top relative to the zero measurement.
Units are according to the `horizontal_units`.
"""
return self._y_displacement
@property
def horizontal_units(self) -> str:
"""
The units of the horizontal XY displacements.
"""
return self._horizontal_units
@property
def vertical_units(self) -> str:
"""
The units of the measurements and distances in the vertical direction.
"""
return self._vertical_units
@property
def status(self) -> SettlementRodMeasurementStatus:
"""
The status of the settlement rod measurement from which the measured settlement
is derived.
"""
return self._status
@property
def status_messages(self) -> List[StatusMessage]:
"""
The list of status messages about the settlement rod measurement from which the
measured settlement is derived.
"""
return self._status_messages
[docs] @cache
def to_dict(self) -> dict:
"""
Convert the measured settlement to a dictionary.
"""
return {
"project_id": self.project.id,
"project_name": self.project.name,
"object_id": self.object_id,
"start_date_time": self.start_date_time,
"date_time": self.date_time,
"days": self.days,
"fill_thickness": self.fill_thickness,
"settlement": self.settlement,
"x_displacement": self.x_displacement,
"y_displacement": self.y_displacement,
"horizontal_units": self.horizontal_units,
"vertical_units": self.vertical_units,
"status": self.status.value,
"status_messages": "\n".join([m.to_string() for m in self.status_messages]),
}