From a6b4023c5071655a17d3e63a73a5b0cfdebed974 Mon Sep 17 00:00:00 2001 From: Pablo Vasconez Date: Mon, 3 Jun 2024 17:04:21 +0200 Subject: [PATCH 1/7] feat(settlement_rod_measurement): add class SettlementRodMeasurement --- pyproject.toml | 1 + requirements.txt | 12 +- src/baec/measurements/__init__.py | 0 .../settlement_rod_measurements.py | 397 ++++++++++++++++ .../test_settlement_rod_measurements.py | 424 ++++++++++++++++++ 5 files changed, 830 insertions(+), 4 deletions(-) create mode 100644 src/baec/measurements/__init__.py create mode 100644 src/baec/measurements/settlement_rod_measurements.py create mode 100644 tests/measurements/test_settlement_rod_measurements.py diff --git a/pyproject.toml b/pyproject.toml index eced000..38b13f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ 'cems-nuclei[client]>=0.5,<1', 'matplotlib>=3.8,<4', "tqdm[notebook]>4,<5", + "pyproj>3,<4", ] license = { file = "LICENSE" } readme = "README.md" diff --git a/requirements.txt b/requirements.txt index 8d295e4..74d7557 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,8 +22,10 @@ cems-nuclei[client]==0.5.5 # via # baec (pyproject.toml) # cems-nuclei -certifi==2024.2.2 - # via requests +certifi==2024.6.2 + # via + # pyproj + # requests charset-normalizer==3.3.2 # via requests click==8.1.3 @@ -56,7 +58,7 @@ executing==2.0.1 # via stack-data flake8==6.0.0 # via baec (pyproject.toml) -fonttools==4.52.4 +fonttools==4.53.0 # via matplotlib idna==3.7 # via requests @@ -164,6 +166,8 @@ pyjwt==2.6.0 # via cems-nuclei pyparsing==3.1.2 # via matplotlib +pyproj==3.6.1 + # via baec (pyproject.toml) pytest==8.2.1 # via baec (pyproject.toml) python-dateutil==2.9.0.post0 @@ -246,7 +250,7 @@ wcwidth==0.2.13 # via prompt-toolkit widgetsnbextension==4.0.11 # via ipywidgets -zipp==3.19.0 +zipp==3.19.1 # via # importlib-metadata # importlib-resources diff --git a/src/baec/measurements/__init__.py b/src/baec/measurements/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/baec/measurements/settlement_rod_measurements.py b/src/baec/measurements/settlement_rod_measurements.py new file mode 100644 index 0000000..19b73f9 --- /dev/null +++ b/src/baec/measurements/settlement_rod_measurements.py @@ -0,0 +1,397 @@ +from __future__ import annotations + +import datetime + +import pyproj + + +class SettlementRodMeasurement: + """ + Represents the measurement of a settlement rod. + + Attributes + ---------- + date_time : datetime.datetime + The date and time of the measurement. + rod_id : str + The ID of the settlement rod. + point_id : str + The ID of the measurement point. + coordinate_epsg_code : int + The EPSG code of the coordinate reference system used. + EPSG codes can be found in https://epsg.io/ . + point_x : float + The X-coordinate of the measurement point. + point_y : float + The Y-coordinate of the measurement point. + point_z : float + The Z-coordinate of the measurement point. + It is the top of the settlement rod. + rod_length : float + The length of the settlement rod. + Note that the settlement rod connects the measurement point with the ground plate, + thus this value is in principle the vertical distance between these two points. + ground_plate_z : float + The corrected Z-coordinate of the ground plate. + Note that this plate is in contact with the original ground surface. + ground_surface_z : float | None, optional + The Z-coordinate of the ground surface, or None if unknown (default: None). + Notes: + - This value in principle corresponds to the top of the fill, if present. + - This value will be typically measured using radar measurements. + temperature : float or None, optional + The temperature at the time of measurement in [°C], or None if unknown (default: None). + voltage : float or None, optional + The voltage measured in [mV], or None if unknown (default: None). + comment : str, optional + Additional comment about the measurement (default: "No comment"). + + Properties + ---------- + coordinate_reference_system : pyproj.CRS + The coordinate reference system used. + ground_plate_z_uncorrected : float + The uncorrected Z-coordinate of the ground plate. + Computed as the difference between point_z and rod_length. + """ + + def __init__( + self, + date_time: datetime.datetime, + rod_id: str, + point_id: str, + coordinate_epsg_code: int, + point_x: float, + point_y: float, + point_z: float, + rod_length: float, + ground_plate_z: float, + ground_surface_z: float, + temperature: float | None = None, + voltage: float | None = None, + comment: str = "No comment", + ) -> None: + """ + Initializes a SettlementRodMeasurement object. + + Parameters + ---------- + date_time : datetime.datetime + The date and time of the measurement. + rod_id : str + The ID of the settlement rod. + point_id : str + The ID of the measurement point. + coordinate_epsg_code : int + The EPSG code of the coordinate reference system used. + EPSG codes can be found in https://epsg.io/ . + point_x : float + The X-coordinate of the measurement point. + point_y : float + The Y-coordinate of the measurement point. + point_z : float + The Z-coordinate of the measurement point. + It is the top of the settlement rod. + rod_length : float + The length of the settlement rod. + Note that the settlement rod connects the measurement point with the ground plate, + thus this value is in principle the vertical distance between these two points. + ground_plate_z : float + The corrected Z-coordinate of the ground plate. + Note that this plate is in contact with the original ground surface. + ground_surface_z : float | None, optional + The Z-coordinate of the ground surface, or None if unknown (default: None). + Notes: + - This value in principle corresponds to the top of the fill, if present. + - This value will be typically measured using radar measurements. + temperature : float or None, optional + The temperature at the time of measurement in [°C], or None if unknown (default: None). + voltage : float or None, optional + The voltage measured in [mV], or None if unknown (default: None). + comment : str, optional + Additional comment about the measurement (default: "No comment"). + + Raises + ------ + TypeError + If the input types are incorrect. + ValueError + - If empty string for `rod_id` or `point_id`. + - If negative value for `coordinate_epsg_code` or `rod_length`. + pyproj.exceptions.CRSError + If no valid coordinate reference system is found for the given EPSG code. + """ + + # Initialize all attributes using private setters. + self._set_date_time(date_time) + self._set_rod_id(rod_id) + self._set_point_id(point_id) + self._set_coordinate_epsg_code(coordinate_epsg_code) + self._set_point_x(point_x) + self._set_point_y(point_y) + self._set_point_z(point_z) + self._set_rod_length(rod_length) + self._set_ground_plate_z(ground_plate_z) + self._set_ground_surface_z(ground_surface_z) + self._set_temperature(temperature) + self._set_voltage(voltage) + self._set_comment(comment) + + # Set the coordinate reference system based on the EPSG code + self._coordinate_reference_system = pyproj.CRS.from_user_input( + coordinate_epsg_code + ) + + def _set_date_time(self, value: datetime.datetime) -> None: + """ + Private setter for date attribute. + """ + if not isinstance(value, datetime.datetime): + raise TypeError("Expected 'datetime.datetime' type for 'date' attribute.") + self._date_time = value + + def _set_rod_id(self, value: str) -> None: + """ + Private setter for rod_id attribute. + """ + if not isinstance(value, str): + raise TypeError("Expected 'str' type for 'rod_id' attribute.") + if value == "": + raise ValueError("Empty string not allowed for 'rod_id' attribute.") + self._rod_id = value + + def _set_point_id(self, value: str) -> None: + """ + Private setter for point_id attribute. + """ + if not isinstance(value, str): + raise TypeError("Expected 'str' type for 'point_id' attribute.") + if value == "": + raise ValueError("Empty string not allowed for 'point_id' attribute.") + self._point_id = value + + def _set_coordinate_epsg_code(self, value: int) -> None: + """ + Private setter for coordinate_epsg_code attribute. + """ + if not isinstance(value, int): + raise TypeError("Expected 'int' type for 'coordinate_epsg_code' attribute.") + if value < 0: + raise ValueError( + "Negative value not allowed for 'coordinate_epsg_code' attribute." + ) + self._coordinate_epsg_code = value + + def _set_point_x(self, value: float) -> None: + """ + Private setter for point_x attribute. + """ + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError("Expected 'float' type for 'point_x' attribute.") + self._point_x = value + + def _set_point_y(self, value: float) -> None: + """ + Private setter for point_y attribute. + """ + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError("Expected 'float' type for 'point_y' attribute.") + self._point_y = value + + def _set_point_z(self, value: float) -> None: + """ + Private setter for point_z attribute. + """ + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError("Expected 'float' type for 'point_z' attribute.") + self._point_z = value + + def _set_rod_length(self, value: float) -> None: + """ + Private setter for rod_length attribute. + """ + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError("Expected 'float' type for 'rod_length' attribute.") + if value < 0: + raise ValueError("Negative value not allowed for 'rod_length' attribute.") + self._rod_length = value + + def _set_ground_surface_z(self, value: float) -> None: + """ + Private setter for ground_surface_z attribute. + """ + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError("Expected 'float' type for 'ground_surface_z' attribute.") + self._ground_surface_z = value + + def _set_ground_plate_z(self, value: float) -> None: + """ + Private setter for ground_plate_z attribute. + """ + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError("Expected 'float' type for 'ground_plate_z' attribute.") + self._ground_plate_z = value + + def _set_ground_plate_z_uncorrected(self, value: float) -> None: + """ + Private setter for ground_plate_z_uncorrected attribute. + """ + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError( + "Expected 'float' type for 'ground_plate_z_uncorrected' attribute." + ) + self._ground_plate_z_uncorrected = value + + def _set_temperature(self, value: float | None) -> None: + """ + Private setter for temperature attribute. + """ + if value is not None: + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError( + "Expected 'float' or 'None' type for 'temperature' attribute." + ) + self._temperature = value + + def _set_voltage(self, value: float | None) -> None: + """ + Private setter for voltage attribute. + """ + if value is not None: + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError( + "Expected 'float' or 'None' type for 'voltage' attribute." + ) + self._voltage = value + + def _set_comment(self, value: str) -> None: + """ + Private setter for comment attribute. + """ + if not isinstance(value, str): + raise TypeError("Expected 'str' type for 'comment' attribute.") + self._comment = value + + @property + def date_time(self) -> datetime.datetime: + """ + The date and time of the measurement. + """ + return self._date_time + + @property + def rod_id(self) -> str: + """ + The ID of the settlement rod. + """ + return self._rod_id + + @property + def point_id(self) -> str: + """ + The ID of the measurement point. + """ + return self._point_id + + @property + def coordinate_epsg_code(self) -> int: + """ + The EPSG code of the coordinate reference system used. + EPSG codes can be found in https://epsg.io/ . + """ + return self._coordinate_epsg_code + + @property + def coordinate_reference_system(self) -> pyproj.CRS: + """ + The coordinate reference system used. + """ + return self._coordinate_reference_system + + @property + def point_x(self) -> float: + """ + The X-coordinate of the measurement point. + """ + return self._point_x + + @property + def point_y(self) -> float: + """ + The Y-coordinate of the measurement point. + """ + return self._point_y + + @property + def point_z(self) -> float: + """ + The Z-coordinate of the measurement point. + """ + return self._point_z + + @property + def rod_length(self) -> float: + """ + The length of the settlement rod. + """ + return self._rod_length + + @property + def ground_plate_z(self) -> float: + """ + The corrected Z-coordinate of the ground plate. + """ + return self._ground_plate_z + + @property + def ground_plate_z_uncorrected(self) -> float: + """ + The uncorrected Z-coordinate of the ground plate. + """ + return self.point_z - self.rod_length + + @property + def ground_surface_z(self) -> float | None: + """ + The Z-coordinate of the ground surface, or None if unknown. + """ + return self._ground_surface_z + + @property + def temperature(self) -> float | None: + """ + The temperature at the time of measurement in [°C], or None if unknown. + """ + return self._temperature + + @property + def voltage(self) -> float | None: + """ + The voltage measured in [mV], or None if unknown. + """ + return self._voltage + + @property + def comment(self) -> str: + """ + Additional comment about the measurement. + """ + return self._comment diff --git a/tests/measurements/test_settlement_rod_measurements.py b/tests/measurements/test_settlement_rod_measurements.py new file mode 100644 index 0000000..8e39f2b --- /dev/null +++ b/tests/measurements/test_settlement_rod_measurements.py @@ -0,0 +1,424 @@ +import datetime + +import pyproj +import pytest + +from baec.measurements.settlement_rod_measurements import SettlementRodMeasurement + + +def test_settlement_rod_measurement_init_with_valid_input() -> None: + """Test initialization of settlement rod measurement with valid input.""" + date_time = datetime.datetime(2024, 4, 9, 4, 0, 0) + rod_id = "BR_003" + point_id = "ZB-02" + coordinate_epsg_code = 28992 + point_x = 123340.266 + point_y = 487597.154 + point_z = 0.807 + rod_length = 2.0 + ground_surface_z = 0.419 + ground_plate_z = -1.193 + ground_plate_z_uncorrected = -1.193 + temperature = 12.0 + voltage = 4232 + comment = "No comment" + coordinate_reference_system = pyproj.CRS.from_user_input(coordinate_epsg_code) + + measurement = SettlementRodMeasurement( + date_time=date_time, + rod_id=rod_id, + point_id=point_id, + coordinate_epsg_code=coordinate_epsg_code, + point_x=point_x, + point_y=point_y, + point_z=point_z, + rod_length=rod_length, + ground_surface_z=ground_surface_z, + ground_plate_z=ground_plate_z, + temperature=temperature, + voltage=voltage, + comment=comment, + ) + + assert measurement.date_time == date_time + assert measurement.rod_id == rod_id + assert measurement.point_id == point_id + assert measurement.coordinate_epsg_code == coordinate_epsg_code + assert measurement.point_x == point_x + assert measurement.point_y == point_y + assert measurement.point_z == point_z + assert measurement.rod_length == rod_length + assert measurement.ground_surface_z == ground_surface_z + assert measurement.ground_plate_z == ground_plate_z + assert measurement.ground_plate_z_uncorrected == ground_plate_z_uncorrected + assert measurement.temperature == temperature + assert measurement.voltage == voltage + assert measurement.comment == comment + assert measurement.coordinate_reference_system == coordinate_reference_system + + +def test_settlement_rod_measurement_init_with_invalid_date_time() -> None: + """Test initialization of settlement rod measurement with invalid date_time.""" + # Invalid date_time: None + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=None, + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_rod_id() -> None: + """Test initialization of settlement rod measurement with invalid rod_id.""" + # Invalid rod_id: None + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id=None, + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + # Invalid rod_id: Empty string + with pytest.raises(ValueError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_point_id() -> None: + """Test initialization of settlement rod measurement with invalid point_id.""" + # Invalid point_id: None + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id=None, + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + # Invalid rod_id: Empty string + with pytest.raises(ValueError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_coordinate_epsg_code() -> None: + """Test initialization of settlement rod measurement with invalid coordinate_epsg_code.""" + # Invalid coordinate_epsg_code: None + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=None, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + # Invalid coordinate_epsg_code: Negative value + with pytest.raises(ValueError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=-28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_point_x() -> None: + """Test initialization of settlement rod measurement with invalid point_x.""" + # Invalid point_x: String value + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x="123340.266", + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_point_y() -> None: + """Test initialization of settlement rod measurement with invalid point_y.""" + # Invalid point_y: None + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=None, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_point_z() -> None: + """Test initialization of settlement rod measurement with invalid point_z.""" + # Invalid point_z: String value + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z="0.807", + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_rod_length() -> None: + """Test initialization of settlement rod measurement with invalid rod_length.""" + # Invalid point_z: String value + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length="2.0", + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + # Invalid rod_length: Negative value + with pytest.raises(ValueError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=-2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_ground_surface_z() -> None: + """Test initialization of settlement rod measurement with invalid ground_surface_z.""" + # Invalid ground_surface_z: String value + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z="-0.419", + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_ground_plate_z() -> None: + """Test initialization of settlement rod measurement with invalid ground_plate_z.""" + # Invalid ground_plate_z: String value + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z="1.193", + temperature=12.0, + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_temperature() -> None: + """Test initialization of settlement rod measurement with invalid temperature.""" + # Invalid temperature: String value + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature="12.0", + voltage=4232, + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_voltage() -> None: + """Test initialization of settlement rod measurement with invalid voltage.""" + # Invalid voltage: String value + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage="4232", + comment="No comment", + ) + + +def test_settlement_rod_measurement_init_with_invalid_comment() -> None: + """Test initialization of settlement rod measurement with invalid comment.""" + # Invalid comment: Integer value + with pytest.raises(TypeError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992, + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment=123, + ) + + +def test_settlement_rod_measurement_init_with_invalid_coordinate_reference_system() -> ( + None +): + """Test initialization of settlement rod measurement with invalid coordinate reference system.""" + with pytest.raises(pyproj.exceptions.CRSError): + SettlementRodMeasurement( + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + rod_id="BR_003", + point_id="ZB-02", + coordinate_epsg_code=28992111, # no coordinate system corresponds to this epsg code. + point_x=123340.266, + point_y=487597.154, + point_z=0.807, + rod_length=2.0, + ground_surface_z=0.419, + ground_plate_z=-1.193, + temperature=12.0, + voltage=4232, + comment="No comment", + ) From 9ce8afde285d0894725765c8e970a2850d94cadf Mon Sep 17 00:00:00 2001 From: Pablo Vasconez Date: Tue, 4 Jun 2024 10:02:55 +0200 Subject: [PATCH 2/7] refactor(settlement_rod_measurement): refactor names of some attributes of SettlementRodMeasurement, improving docstrings and adding sphinx docs --- docs/index.rst | 2 +- docs/tree/figures/settlement_rod.png | Bin 0 -> 40356 bytes docs/tree/reference.rst | 24 ++ pyproject.toml | 6 +- .../settlement_rod_measurements.py | 234 ++++++++---------- .../test_settlement_rod_measurements.py | 214 ++++++++-------- 6 files changed, 233 insertions(+), 247 deletions(-) create mode 100644 docs/tree/figures/settlement_rod.png create mode 100644 docs/tree/reference.rst diff --git a/docs/index.rst b/docs/index.rst index 84d0083..b8bc5a2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,4 +14,4 @@ Indices and tables tree/getting_started.rst - + tree/reference.rst diff --git a/docs/tree/figures/settlement_rod.png b/docs/tree/figures/settlement_rod.png new file mode 100644 index 0000000000000000000000000000000000000000..d6ccb4aad090faaa2643c586b0c90252129ecf2d GIT binary patch literal 40356 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 literal 0 HcmV?d00001 diff --git a/docs/tree/reference.rst b/docs/tree/reference.rst new file mode 100644 index 0000000..82133e6 --- /dev/null +++ b/docs/tree/reference.rst @@ -0,0 +1,24 @@ +.. _reference: + +Reference +========= + +.. _settlementRodMeasurement: +Settlement Rod Measurement +---------------------------- + +A settlement rod device consists of a rod and a (bottom) settlement plate (see figure below). The measurements are taken at the top of the rod (i.e. the `measurement point`) +and since the `vertical offset` between the measurement point is known, the settlement of at the bottom of the plate `plate bottom z` can be also derived. Optionally, +the `ground surface z` can be measured by performing for instance radar measurements. + +.. image:: figures/settlement_rod.png + +The class `SettlementRodMeasurement` presented below stores the data of a single settlement rod measurement. + +.. autoclass:: baec.measurements.settlement_rod_measurements.SettlementRodMeasurement + :members: + :inherited-members: + :member-order: bysource + + .. automethod:: __init__ + diff --git a/pyproject.toml b/pyproject.toml index 38b13f5..a9fd8e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ ensure_newline_before_comments = true line_length = 88 [tool.mypy] -files = ["src/pypilecore"] +files = ["src/baec"] mypy_path = 'src' namespace_packages = true show_error_codes = true @@ -85,10 +85,8 @@ module = [ "matplotlib.*", "requests.*", "nuclei.*", - "pygef.*", - "natsort.*", - "shapely.*", "pytest.*", "scipy.*", + "pyproj.*", ] ignore_missing_imports = true diff --git a/src/baec/measurements/settlement_rod_measurements.py b/src/baec/measurements/settlement_rod_measurements.py index 19b73f9..5535b84 100644 --- a/src/baec/measurements/settlement_rod_measurements.py +++ b/src/baec/measurements/settlement_rod_measurements.py @@ -7,52 +7,7 @@ class SettlementRodMeasurement: """ - Represents the measurement of a settlement rod. - - Attributes - ---------- - date_time : datetime.datetime - The date and time of the measurement. - rod_id : str - The ID of the settlement rod. - point_id : str - The ID of the measurement point. - coordinate_epsg_code : int - The EPSG code of the coordinate reference system used. - EPSG codes can be found in https://epsg.io/ . - point_x : float - The X-coordinate of the measurement point. - point_y : float - The Y-coordinate of the measurement point. - point_z : float - The Z-coordinate of the measurement point. - It is the top of the settlement rod. - rod_length : float - The length of the settlement rod. - Note that the settlement rod connects the measurement point with the ground plate, - thus this value is in principle the vertical distance between these two points. - ground_plate_z : float - The corrected Z-coordinate of the ground plate. - Note that this plate is in contact with the original ground surface. - ground_surface_z : float | None, optional - The Z-coordinate of the ground surface, or None if unknown (default: None). - Notes: - - This value in principle corresponds to the top of the fill, if present. - - This value will be typically measured using radar measurements. - temperature : float or None, optional - The temperature at the time of measurement in [°C], or None if unknown (default: None). - voltage : float or None, optional - The voltage measured in [mV], or None if unknown (default: None). - comment : str, optional - Additional comment about the measurement (default: "No comment"). - - Properties - ---------- - coordinate_reference_system : pyproj.CRS - The coordinate reference system used. - ground_plate_z_uncorrected : float - The uncorrected Z-coordinate of the ground plate. - Computed as the difference between point_z and rod_length. + Represents a single settlement rod measurement. """ def __init__( @@ -61,12 +16,12 @@ def __init__( rod_id: str, point_id: str, coordinate_epsg_code: int, - point_x: float, - point_y: float, - point_z: float, - rod_length: float, - ground_plate_z: float, - ground_surface_z: float, + x: float, + y: float, + z: float, + vertical_offset: float, + plate_bottom_z: float, + ground_surface_z: float | None = None, temperature: float | None = None, voltage: float | None = None, comment: str = "No comment", @@ -84,42 +39,46 @@ def __init__( The ID of the measurement point. coordinate_epsg_code : int The EPSG code of the coordinate reference system used. - EPSG codes can be found in https://epsg.io/ . - point_x : float - The X-coordinate of the measurement point. - point_y : float - The Y-coordinate of the measurement point. - point_z : float + EPSG codes can be found in https://epsg.io/. + x : float + The X-coordinate of the measurement point. Units are according to the `coordinate_reference_system`. + y : float + The Y-coordinate of the measurement point. Units are according to the `coordinate_reference_system`. + z : float The Z-coordinate of the measurement point. It is the top of the settlement rod. - rod_length : float - The length of the settlement rod. - Note that the settlement rod connects the measurement point with the ground plate, - thus this value is in principle the vertical distance between these two points. - ground_plate_z : float - The corrected Z-coordinate of the ground plate. - Note that this plate is in contact with the original ground surface. + Units are according to the `coordinate_reference_system`. + vertical_offset : float + The vertical offset distance between the measurement point and the bottom of the settlement plate. + It is in principle the rod length plus the plate thickness. + Units are according to the `coordinate_reference_system`. + plate_bottom_z : float + The corrected Z-coordinate at the bottom of the settlement plate. + Note that the bottom of the plate is in principle the original ground surface. + Units are according to the `coordinate_reference_system`. ground_surface_z : float | None, optional - The Z-coordinate of the ground surface, or None if unknown (default: None). - Notes: - - This value in principle corresponds to the top of the fill, if present. - - This value will be typically measured using radar measurements. + The Z-coordinate of the ground surface, or None if unknown (default: None). + Notes: + - This value in principle corresponds to the top of the fill, if present. + - This value will be typically measured using radar measurements. + Units are according to the `coordinate_reference_system`. temperature : float or None, optional The temperature at the time of measurement in [°C], or None if unknown (default: None). voltage : float or None, optional The voltage measured in [mV], or None if unknown (default: None). comment : str, optional - Additional comment about the measurement (default: "No comment"). + Comment about the measurement (default: "No comment"). Raises ------ TypeError If the input types are incorrect. ValueError - - If empty string for `rod_id` or `point_id`. - - If negative value for `coordinate_epsg_code` or `rod_length`. + If empty string for `rod_id` or `point_id`. + ValueError + If negative value for `coordinate_epsg_code` or `vertical_offset`. pyproj.exceptions.CRSError - If no valid coordinate reference system is found for the given EPSG code. + If no valid coordinate reference system is found for the given `coordinate_epsg_code`. """ # Initialize all attributes using private setters. @@ -127,11 +86,11 @@ def __init__( self._set_rod_id(rod_id) self._set_point_id(point_id) self._set_coordinate_epsg_code(coordinate_epsg_code) - self._set_point_x(point_x) - self._set_point_y(point_y) - self._set_point_z(point_z) - self._set_rod_length(rod_length) - self._set_ground_plate_z(ground_plate_z) + self._set_x(x) + self._set_y(y) + self._set_z(z) + self._set_vertical_offset(vertical_offset) + self._set_plate_bottom_z(plate_bottom_z) self._set_ground_surface_z(ground_surface_z) self._set_temperature(temperature) self._set_voltage(voltage) @@ -182,79 +141,72 @@ def _set_coordinate_epsg_code(self, value: int) -> None: ) self._coordinate_epsg_code = value - def _set_point_x(self, value: float) -> None: + def _set_x(self, value: float) -> None: """ - Private setter for point_x attribute. + Private setter for x attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'point_x' attribute.") - self._point_x = value + raise TypeError("Expected 'float' type for 'x' attribute.") + self._x = value - def _set_point_y(self, value: float) -> None: + def _set_y(self, value: float) -> None: """ - Private setter for point_y attribute. + Private setter for y attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'point_y' attribute.") - self._point_y = value + raise TypeError("Expected 'float' type 'y' attribute.") + self._y = value - def _set_point_z(self, value: float) -> None: + def _set_z(self, value: float) -> None: """ - Private setter for point_z attribute. + Private setter for z attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'point_z' attribute.") - self._point_z = value + raise TypeError("Expected 'float' type for 'z' attribute.") + self._z = value - def _set_rod_length(self, value: float) -> None: + def _set_vertical_offset(self, value: float) -> None: """ - Private setter for rod_length attribute. + Private setter for vertical_offset attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'rod_length' attribute.") + raise TypeError("Expected 'float' type for 'vertical_offset' attribute.") if value < 0: - raise ValueError("Negative value not allowed for 'rod_length' attribute.") - self._rod_length = value - - def _set_ground_surface_z(self, value: float) -> None: - """ - Private setter for ground_surface_z attribute. - """ - if isinstance(value, int): - value = float(value) - if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'ground_surface_z' attribute.") - self._ground_surface_z = value + raise ValueError( + "Negative value not allowed for 'vertical_offset' attribute." + ) + self._vertical_offset = value - def _set_ground_plate_z(self, value: float) -> None: + def _set_plate_bottom_z(self, value: float) -> None: """ - Private setter for ground_plate_z attribute. + Private setter for plate_bottom_z attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'ground_plate_z' attribute.") - self._ground_plate_z = value + raise TypeError("Expected 'float' type for 'plate_bottom_z' attribute.") + self._plate_bottom_z = value - def _set_ground_plate_z_uncorrected(self, value: float) -> None: + def _set_ground_surface_z(self, value: float | None) -> None: """ - Private setter for ground_plate_z_uncorrected attribute. + Private setter for ground_surface_z attribute. """ - if isinstance(value, int): - value = float(value) - if not isinstance(value, float): - raise TypeError( - "Expected 'float' type for 'ground_plate_z_uncorrected' attribute." - ) - self._ground_plate_z_uncorrected = value + if value is not None: + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError( + "Expected 'float' or 'None' type for 'ground_surface_z' attribute." + ) + self._ground_surface_z = value def _set_temperature(self, value: float | None) -> None: """ @@ -315,7 +267,7 @@ def point_id(self) -> str: def coordinate_epsg_code(self) -> int: """ The EPSG code of the coordinate reference system used. - EPSG codes can be found in https://epsg.io/ . + EPSG codes can be found in https://epsg.io/. """ return self._coordinate_epsg_code @@ -323,50 +275,62 @@ def coordinate_epsg_code(self) -> int: def coordinate_reference_system(self) -> pyproj.CRS: """ The coordinate reference system used. + It is a `pyproj.CRS` object (see https://pyproj4.github.io/pyproj/stable/api/crs/crs.html). + It is determined based on the `coordinate_epsg_code`. """ return self._coordinate_reference_system @property - def point_x(self) -> float: + def x(self) -> float: """ The X-coordinate of the measurement point. + Units are according to the `coordinate_reference_system`. """ - return self._point_x + return self._x @property - def point_y(self) -> float: + def y(self) -> float: """ The Y-coordinate of the measurement point. + Units are according to the `coordinate_reference_system`. """ - return self._point_y + return self._y @property - def point_z(self) -> float: + def z(self) -> float: """ The Z-coordinate of the measurement point. + It is the top of the settlement rod. + Units are according to the `coordinate_reference_system`. """ - return self._point_z + return self._z @property - def rod_length(self) -> float: + def vertical_offset(self) -> float: """ - The length of the settlement rod. + The vertical offset distance between the measurement point and the bottom of the settlement plate. + It is in principle the rod length plus the plate thickness. + Units are according to the `coordinate_reference_system`. """ - return self._rod_length + return self._vertical_offset @property - def ground_plate_z(self) -> float: + def plate_bottom_z(self) -> float: """ - The corrected Z-coordinate of the ground plate. + The corrected Z-coordinate at the bottom of the settlement plate. + Note that the bottom of the plate is in principle the original ground surface. + Units are according to the `coordinate_reference_system`. """ - return self._ground_plate_z + return self._plate_bottom_z @property - def ground_plate_z_uncorrected(self) -> float: + def plate_bottom_z_uncorrected(self) -> float: """ - The uncorrected Z-coordinate of the ground plate. + The uncorrected Z-coordinate at the bottom of the settlement plate. + It is computed as the difference beteen the Z-coordinate of the measurement point and the vertical offset. + Units are according to the `coordinate_reference_system`. """ - return self.point_z - self.rod_length + return self.z - self.vertical_offset @property def ground_surface_z(self) -> float | None: @@ -392,6 +356,6 @@ def voltage(self) -> float | None: @property def comment(self) -> str: """ - Additional comment about the measurement. + Comment about the measurement. """ return self._comment diff --git a/tests/measurements/test_settlement_rod_measurements.py b/tests/measurements/test_settlement_rod_measurements.py index 8e39f2b..fd1e654 100644 --- a/tests/measurements/test_settlement_rod_measurements.py +++ b/tests/measurements/test_settlement_rod_measurements.py @@ -12,13 +12,13 @@ def test_settlement_rod_measurement_init_with_valid_input() -> None: rod_id = "BR_003" point_id = "ZB-02" coordinate_epsg_code = 28992 - point_x = 123340.266 - point_y = 487597.154 - point_z = 0.807 - rod_length = 2.0 + x = 123340.266 + y = 487597.154 + z = 0.807 + vertical_offset = 2.0 ground_surface_z = 0.419 - ground_plate_z = -1.193 - ground_plate_z_uncorrected = -1.193 + plate_bottom_z = -1.193 + plate_bottom_z_uncorrected = -1.193 temperature = 12.0 voltage = 4232 comment = "No comment" @@ -29,12 +29,12 @@ def test_settlement_rod_measurement_init_with_valid_input() -> None: rod_id=rod_id, point_id=point_id, coordinate_epsg_code=coordinate_epsg_code, - point_x=point_x, - point_y=point_y, - point_z=point_z, - rod_length=rod_length, + x=x, + y=y, + z=z, + vertical_offset=vertical_offset, ground_surface_z=ground_surface_z, - ground_plate_z=ground_plate_z, + plate_bottom_z=plate_bottom_z, temperature=temperature, voltage=voltage, comment=comment, @@ -44,13 +44,13 @@ def test_settlement_rod_measurement_init_with_valid_input() -> None: assert measurement.rod_id == rod_id assert measurement.point_id == point_id assert measurement.coordinate_epsg_code == coordinate_epsg_code - assert measurement.point_x == point_x - assert measurement.point_y == point_y - assert measurement.point_z == point_z - assert measurement.rod_length == rod_length + assert measurement.x == x + assert measurement.y == y + assert measurement.z == z + assert measurement.vertical_offset == vertical_offset assert measurement.ground_surface_z == ground_surface_z - assert measurement.ground_plate_z == ground_plate_z - assert measurement.ground_plate_z_uncorrected == ground_plate_z_uncorrected + assert measurement.plate_bottom_z == plate_bottom_z + assert measurement.plate_bottom_z_uncorrected == plate_bottom_z_uncorrected assert measurement.temperature == temperature assert measurement.voltage == voltage assert measurement.comment == comment @@ -66,12 +66,12 @@ def test_settlement_rod_measurement_init_with_invalid_date_time() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -87,12 +87,12 @@ def test_settlement_rod_measurement_init_with_invalid_rod_id() -> None: rod_id=None, point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -105,12 +105,12 @@ def test_settlement_rod_measurement_init_with_invalid_rod_id() -> None: rod_id="", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -126,12 +126,12 @@ def test_settlement_rod_measurement_init_with_invalid_point_id() -> None: rod_id="BR_003", point_id=None, coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -144,12 +144,12 @@ def test_settlement_rod_measurement_init_with_invalid_point_id() -> None: rod_id="BR_003", point_id="", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -165,12 +165,12 @@ def test_settlement_rod_measurement_init_with_invalid_coordinate_epsg_code() -> rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=None, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -183,12 +183,12 @@ def test_settlement_rod_measurement_init_with_invalid_coordinate_epsg_code() -> rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=-28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -204,12 +204,12 @@ def test_settlement_rod_measurement_init_with_invalid_point_x() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x="123340.266", - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x="123340.266", + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -225,12 +225,12 @@ def test_settlement_rod_measurement_init_with_invalid_point_y() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=None, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=None, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -246,12 +246,12 @@ def test_settlement_rod_measurement_init_with_invalid_point_z() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z="0.807", - rod_length=2.0, + x=123340.266, + y=487597.154, + z="0.807", + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -267,12 +267,12 @@ def test_settlement_rod_measurement_init_with_invalid_rod_length() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length="2.0", + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset="2.0", ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -285,12 +285,12 @@ def test_settlement_rod_measurement_init_with_invalid_rod_length() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=-2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=-2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -306,12 +306,12 @@ def test_settlement_rod_measurement_init_with_invalid_ground_surface_z() -> None rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z="-0.419", - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", @@ -327,12 +327,12 @@ def test_settlement_rod_measurement_init_with_invalid_ground_plate_z() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z="1.193", + plate_bottom_z="1.193", temperature=12.0, voltage=4232, comment="No comment", @@ -348,12 +348,12 @@ def test_settlement_rod_measurement_init_with_invalid_temperature() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature="12.0", voltage=4232, comment="No comment", @@ -369,12 +369,12 @@ def test_settlement_rod_measurement_init_with_invalid_voltage() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage="4232", comment="No comment", @@ -390,12 +390,12 @@ def test_settlement_rod_measurement_init_with_invalid_comment() -> None: rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992, - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment=123, @@ -412,12 +412,12 @@ def test_settlement_rod_measurement_init_with_invalid_coordinate_reference_syste rod_id="BR_003", point_id="ZB-02", coordinate_epsg_code=28992111, # no coordinate system corresponds to this epsg code. - point_x=123340.266, - point_y=487597.154, - point_z=0.807, - rod_length=2.0, + x=123340.266, + y=487597.154, + z=0.807, + vertical_offset=2.0, ground_surface_z=0.419, - ground_plate_z=-1.193, + plate_bottom_z=-1.193, temperature=12.0, voltage=4232, comment="No comment", From 993ec083598e5c3176eb58a6f6fa69f68bcd5138 Mon Sep 17 00:00:00 2001 From: Pablo Vasconez Date: Thu, 6 Jun 2024 16:53:15 +0200 Subject: [PATCH 3/7] refactor(settlement_rod_measurement): refactor SettlementRodMeasurement according to comments from Michel (CRUX) and Daniel (BouwRisk) --- .github/workflows/lint_test.yaml | 30 +- pyproject.toml | 3 +- requirements.txt | 92 ++++- src/baec/measurements/measurement_device.py | 90 +++++ ...ments.py => settlement_rod_measurement.py} | 231 ++++++----- src/baec/project.py | 87 +++++ tests/measurements/test_measurement_device.py | 55 +++ ....py => test_settlement_rod_measurement.py} | 362 +++++++++--------- tests/test_project.py | 50 +++ 9 files changed, 703 insertions(+), 297 deletions(-) create mode 100644 src/baec/measurements/measurement_device.py rename src/baec/measurements/{settlement_rod_measurements.py => settlement_rod_measurement.py} (58%) create mode 100644 src/baec/project.py create mode 100644 tests/measurements/test_measurement_device.py rename tests/measurements/{test_settlement_rod_measurements.py => test_settlement_rod_measurement.py} (53%) create mode 100644 tests/test_project.py diff --git a/.github/workflows/lint_test.yaml b/.github/workflows/lint_test.yaml index d1c864f..fea3ebc 100644 --- a/.github/workflows/lint_test.yaml +++ b/.github/workflows/lint_test.yaml @@ -38,21 +38,21 @@ jobs: - name: Test run: coverage run -m pytest - - name: Post coverage results - uses: coverallsapp/github-action@v2 - with: - flag-name: run-${{ matrix.python-version }} - parallel: true - - finish: - needs: test - if: ${{ always() }} - runs-on: ubuntu-latest - steps: - - name: Coveralls Finished - uses: coverallsapp/github-action@v2 - with: - parallel-finished: true + # - name: Post coverage results + # uses: coverallsapp/github-action@v2 + # with: + # flag-name: run-${{ matrix.python-version }} + # parallel: true + + # finish: + # needs: test + # if: ${{ always() }} + # runs-on: ubuntu-latest + # steps: + # - name: Coveralls Finished + # uses: coverallsapp/github-action@v2 + # with: + # parallel-finished: true lint: name: Formatting check diff --git a/pyproject.toml b/pyproject.toml index a9fd8e3..bdca795 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,8 @@ docs = [ "sphinx-autodoc-typehints==1.22", "ipython==8.11.0", "asteroid-sphinx-theme==0.0.3", - "sphinx_rtd_theme==1.2.0", + "sphinx_rtd_theme>1.2,<2", + "enum-tools[sphinx]>0.12,<0.13", ] # lint dependencies from github super-linter v5 # See https://github.com/super-linter/super-linter/tree/main/dependencies/python diff --git a/requirements.txt b/requirements.txt index 74d7557..4ba8d2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,18 +6,30 @@ # alabaster==0.7.16 # via sphinx +apeye==1.4.1 + # via sphinx-toolbox +apeye-core==1.1.5 + # via apeye asteroid-sphinx-theme==0.0.3 # via baec (pyproject.toml) asttokens==2.4.1 # via stack-data +autodocsumm==0.2.12 + # via sphinx-toolbox babel==2.15.0 # via sphinx backcall==0.2.0 # via ipython +beautifulsoup4==4.12.3 + # via sphinx-toolbox black[jupyter]==23.10.1 # via # baec (pyproject.toml) # black +cachecontrol[filecache]==0.14.0 + # via + # cachecontrol + # sphinx-toolbox cems-nuclei[client]==0.5.5 # via # baec (pyproject.toml) @@ -42,26 +54,49 @@ coverage[toml]==7.5.3 # coveralls coveralls==4.0.1 # via baec (pyproject.toml) +cssutils==2.11.1 + # via dict2css cycler==0.12.1 # via matplotlib decorator==5.1.1 # via ipython +dict2css==0.3.0.post1 + # via sphinx-toolbox docopt==0.6.2 # via coveralls docutils==0.18.1 # via # sphinx + # sphinx-prompt # sphinx-rtd-theme + # sphinx-tabs + # sphinx-toolbox +domdf-python-tools==3.8.1 + # via + # apeye + # apeye-core + # dict2css + # sphinx-toolbox +enum-tools[sphinx]==0.12.0 + # via baec (pyproject.toml) exceptiongroup==1.2.1 # via pytest executing==2.0.1 # via stack-data +filelock==3.14.0 + # via + # cachecontrol + # sphinx-toolbox flake8==6.0.0 # via baec (pyproject.toml) fonttools==4.53.0 # via matplotlib +html5lib==1.1 + # via sphinx-toolbox idna==3.7 - # via requests + # via + # apeye-core + # requests imagesize==1.4.1 # via sphinx importlib-metadata==7.1.0 @@ -83,13 +118,17 @@ isort==5.12.0 jedi==0.19.1 # via ipython jinja2==3.1.4 - # via sphinx + # via + # sphinx + # sphinx-jinja2-compat jupyterlab-widgets==3.0.11 # via ipywidgets kiwisolver==1.4.5 # via matplotlib markupsafe==2.1.5 - # via jinja2 + # via + # jinja2 + # sphinx-jinja2-compat matplotlib==3.9.0 # via baec (pyproject.toml) matplotlib-inline==0.1.7 @@ -98,6 +137,10 @@ mccabe==0.7.0 # via # baec (pyproject.toml) # flake8 +more-itertools==10.2.0 + # via cssutils +msgpack==1.0.8 + # via cachecontrol mypy==1.6.1 # via baec (pyproject.toml) mypy-extensions==1.0.0 @@ -105,6 +148,8 @@ mypy-extensions==1.0.0 # baec (pyproject.toml) # black # mypy +natsort==8.4.0 + # via domdf-python-tools numpy==1.26.4 # via # baec (pyproject.toml) @@ -140,6 +185,7 @@ pillow==10.3.0 # via matplotlib platformdirs==3.5.1 # via + # apeye # baec (pyproject.toml) # black pluggy==1.5.0 @@ -160,8 +206,11 @@ pyflakes==3.0.1 # flake8 pygments==2.18.0 # via + # enum-tools # ipython # sphinx + # sphinx-prompt + # sphinx-tabs pyjwt==2.6.0 # via cems-nuclei pyparsing==3.1.2 @@ -178,26 +227,52 @@ pytz==2024.1 # via pandas requests==2.32.3 # via + # apeye + # cachecontrol # cems-nuclei # coveralls # sphinx +ruamel-yaml==0.18.6 + # via sphinx-toolbox +ruamel-yaml-clib==0.2.8 + # via ruamel-yaml six==1.16.0 # via # asttokens + # html5lib # python-dateutil snowballstemmer==2.2.0 # via sphinx +soupsieve==2.5 + # via beautifulsoup4 sphinx==6.1.3 # via # asteroid-sphinx-theme + # autodocsumm # baec (pyproject.toml) + # enum-tools # sphinx-autodoc-typehints + # sphinx-prompt # sphinx-rtd-theme + # sphinx-tabs + # sphinx-toolbox # sphinxcontrib-jquery sphinx-autodoc-typehints==1.22 + # via + # baec (pyproject.toml) + # sphinx-toolbox +sphinx-jinja2-compat==0.2.0.post1 + # via + # enum-tools + # sphinx-toolbox +sphinx-prompt==1.6.0 + # via sphinx-toolbox +sphinx-rtd-theme==2.0.0 # via baec (pyproject.toml) -sphinx-rtd-theme==1.2.0 - # via baec (pyproject.toml) +sphinx-tabs==3.4.5 + # via sphinx-toolbox +sphinx-toolbox==3.5.0 + # via enum-tools sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 @@ -214,6 +289,8 @@ sphinxcontrib-serializinghtml==1.1.10 # via sphinx stack-data==0.6.3 # via ipython +tabulate==0.9.0 + # via sphinx-toolbox tokenize-rt==5.2.0 # via black tomli==2.0.1 @@ -241,13 +318,18 @@ typing-extensions==4.7.1 # via # baec (pyproject.toml) # black + # domdf-python-tools + # enum-tools # mypy + # sphinx-toolbox tzdata==2024.1 # via pandas urllib3==2.2.1 # via requests wcwidth==0.2.13 # via prompt-toolkit +webencodings==0.5.1 + # via html5lib widgetsnbextension==4.0.11 # via ipywidgets zipp==3.19.1 diff --git a/src/baec/measurements/measurement_device.py b/src/baec/measurements/measurement_device.py new file mode 100644 index 0000000..930c770 --- /dev/null +++ b/src/baec/measurements/measurement_device.py @@ -0,0 +1,90 @@ +from __future__ import annotations + + +class MeasurementDevice: + """ + Represents a measurement device. + """ + + def __init__( + self, + id_: str, + qr_code: str | None = None, + ) -> None: + """ + Initializes a MeasurementDevice object. + + Parameters + ---------- + id_ : str + The ID of the measurement device. + qr_code : str | None, optional + The QR code of the measurement device, or None if unknown (default: None). + + Raises + ------ + TypeError + If the input types are incorrect. + ValueError + If empty string for `id_` or `qr_code`. + """ + + # Initialize all attributes using private setters. + self._set_id(id_) + self._set_qr_code(qr_code) + + def _set_id(self, value: str) -> None: + """ + Private setter for id attribute. + """ + if not isinstance(value, str): + raise TypeError("Expected 'str' type for 'id' attribute.") + if value == "": + raise ValueError("Empty string not allowed for 'id' attribute.") + self._id = value + + def _set_qr_code(self, value: str | None) -> None: + """ + Private setter for qr_code attribute. + """ + if value is not None: + if not isinstance(value, str): + raise TypeError( + "Expected 'str' or 'None' type for 'qr_code' attribute." + ) + if value == "": + raise ValueError("Empty string not allowed for 'qr_code' attribute.") + self._qr_code = value + + @property + def id(self) -> str: + """ + The ID of the measurement device. + """ + return self._id + + @property + def qr_code(self) -> str | None: + """ + The QR-code of the measurement device. + """ + return self._qr_code + + def __eq__(self, other: object) -> bool: + """ + Check if two MeasurementDevice objects are equal. + It compares the `id` attribute. + + Parameters + ---------- + other : object + The object to compare. + + Returns + ------- + bool + True if the objects are equal, False otherwise. + """ + if not isinstance(other, MeasurementDevice): + return False + return self.id == other.id diff --git a/src/baec/measurements/settlement_rod_measurements.py b/src/baec/measurements/settlement_rod_measurement.py similarity index 58% rename from src/baec/measurements/settlement_rod_measurements.py rename to src/baec/measurements/settlement_rod_measurement.py index 5535b84..e3bc184 100644 --- a/src/baec/measurements/settlement_rod_measurements.py +++ b/src/baec/measurements/settlement_rod_measurement.py @@ -1,9 +1,27 @@ from __future__ import annotations import datetime +from enum import Enum import pyproj +from baec.measurements.measurement_device import MeasurementDevice +from baec.project import Project + + +class SettlementRodMeasurementStatus(Enum): + """Represents the status of a settlement rod measurement.""" + + OK = "ok" + DISTURBED = "disturbed" + EXPIRED = "expired" + RELOCATED = "relocated" + ROD_IS_EXTENDED = "rod_is_extended" + CROOKED = "crooked" + DESELECTED = "deselected" + FICTIONAL = "fictional" + UNKNOWN = "unknown" + class SettlementRodMeasurement: """ @@ -12,34 +30,40 @@ class SettlementRodMeasurement: def __init__( self, + project: Project, + device: MeasurementDevice, + object_id: str, date_time: datetime.datetime, - rod_id: str, - point_id: str, - coordinate_epsg_code: int, + coordinate_reference_system: pyproj.CRS, x: float, y: float, z: float, - vertical_offset: float, + rod_length: float, plate_bottom_z: float, - ground_surface_z: float | None = None, + ground_surface_z: float, + status: SettlementRodMeasurementStatus, temperature: float | None = None, voltage: float | None = None, - comment: str = "No comment", + comment: str = "", ) -> None: """ Initializes a SettlementRodMeasurement object. Parameters ---------- + project : Project + The project which the measurement belongs to. + device : MeasurementDevice + The measurement device. + date_time : datetime.datetime + The date and time of the measurement. + object_id : str + The ID of the measured object. date_time : datetime.datetime The date and time of the measurement. - rod_id : str - The ID of the settlement rod. - point_id : str - The ID of the measurement point. - coordinate_epsg_code : int - The EPSG code of the coordinate reference system used. - EPSG codes can be found in https://epsg.io/. + coordinate_reference_system : pyproj.CRS + The coordinate reference system of the spatial measurements. + It is a `pyproj.CRS` object (see https://pyproj4.github.io/pyproj/stable/api/crs/crs.html). x : float The X-coordinate of the measurement point. Units are according to the `coordinate_reference_system`. y : float @@ -48,98 +72,98 @@ def __init__( The Z-coordinate of the measurement point. It is the top of the settlement rod. Units are according to the `coordinate_reference_system`. - vertical_offset : float - The vertical offset distance between the measurement point and the bottom of the settlement plate. - It is in principle the rod length plus the plate thickness. + rod_length : float + The length of the settlement rod including the thickness of the settlement plate. + It is in principle the vertical distance between the measurement point and the bottom of the settlement plate. Units are according to the `coordinate_reference_system`. plate_bottom_z : float The corrected Z-coordinate at the bottom of the settlement plate. Note that the bottom of the plate is in principle the original ground surface. Units are according to the `coordinate_reference_system`. - ground_surface_z : float | None, optional - The Z-coordinate of the ground surface, or None if unknown (default: None). - Notes: - - This value in principle corresponds to the top of the fill, if present. - - This value will be typically measured using radar measurements. + ground_surface_z : float + The Z-coordinate of the ground surface. + It is in principle the top of the fill, if present. Units are according to the `coordinate_reference_system`. + status: SettlementRodMeasurementStatus + The status of the measurement. temperature : float or None, optional The temperature at the time of measurement in [°C], or None if unknown (default: None). voltage : float or None, optional The voltage measured in [mV], or None if unknown (default: None). comment : str, optional - Comment about the measurement (default: "No comment"). + Additional comment about the measurement (default: ""). Raises ------ TypeError If the input types are incorrect. ValueError - If empty string for `rod_id` or `point_id`. - ValueError - If negative value for `coordinate_epsg_code` or `vertical_offset`. - pyproj.exceptions.CRSError - If no valid coordinate reference system is found for the given `coordinate_epsg_code`. + If empty string for `object_id`. + If negative value for `rod_length`. """ # Initialize all attributes using private setters. + self._set_project(project) + self._set_device(device) + self._set_object_id(object_id) self._set_date_time(date_time) - self._set_rod_id(rod_id) - self._set_point_id(point_id) - self._set_coordinate_epsg_code(coordinate_epsg_code) + self._set_coordinate_reference_system(coordinate_reference_system) self._set_x(x) self._set_y(y) self._set_z(z) - self._set_vertical_offset(vertical_offset) + self._set_rod_length(rod_length) self._set_plate_bottom_z(plate_bottom_z) self._set_ground_surface_z(ground_surface_z) + self._set_status(status) self._set_temperature(temperature) self._set_voltage(voltage) self._set_comment(comment) - # Set the coordinate reference system based on the EPSG code - self._coordinate_reference_system = pyproj.CRS.from_user_input( - coordinate_epsg_code - ) + 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_date_time(self, value: datetime.datetime) -> None: + def _set_device(self, value: MeasurementDevice) -> None: """ - Private setter for date attribute. + Private setter for device attribute. """ - if not isinstance(value, datetime.datetime): - raise TypeError("Expected 'datetime.datetime' type for 'date' attribute.") - self._date_time = value + if not isinstance(value, MeasurementDevice): + raise TypeError("Expected 'MeasurementDevice' type for 'device' attribute.") + self._device = value - def _set_rod_id(self, value: str) -> None: + def _set_object_id(self, value: str) -> None: """ - Private setter for rod_id attribute. + Private setter for object_id attribute. """ if not isinstance(value, str): - raise TypeError("Expected 'str' type for 'rod_id' attribute.") + raise TypeError("Expected 'str' type for 'object_id' attribute.") if value == "": - raise ValueError("Empty string not allowed for 'rod_id' attribute.") - self._rod_id = value + raise ValueError("Empty string not allowed for 'object_id' attribute.") + self._object_id = value - def _set_point_id(self, value: str) -> None: + def _set_date_time(self, value: datetime.datetime) -> None: """ - Private setter for point_id attribute. + Private setter for date_time attribute. """ - if not isinstance(value, str): - raise TypeError("Expected 'str' type for 'point_id' attribute.") - if value == "": - raise ValueError("Empty string not allowed for 'point_id' attribute.") - self._point_id = value + if not isinstance(value, datetime.datetime): + raise TypeError( + "Expected 'datetime.datetime' type for 'date_time' attribute." + ) + self._date_time = value - def _set_coordinate_epsg_code(self, value: int) -> None: + def _set_coordinate_reference_system(self, value: pyproj.CRS) -> None: """ - Private setter for coordinate_epsg_code attribute. + Private setter for coordinate_reference_system attribute. """ - if not isinstance(value, int): - raise TypeError("Expected 'int' type for 'coordinate_epsg_code' attribute.") - if value < 0: - raise ValueError( - "Negative value not allowed for 'coordinate_epsg_code' attribute." + if not isinstance(value, pyproj.CRS): + raise TypeError( + "Expected 'pyproj.CRS' type for 'coordinate_reference_system' attribute." ) - self._coordinate_epsg_code = value + self._coordinate_reference_system = value def _set_x(self, value: float) -> None: """ @@ -171,19 +195,17 @@ def _set_z(self, value: float) -> None: raise TypeError("Expected 'float' type for 'z' attribute.") self._z = value - def _set_vertical_offset(self, value: float) -> None: + def _set_rod_length(self, value: float) -> None: """ - Private setter for vertical_offset attribute. + Private setter for rod_length attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'vertical_offset' attribute.") + raise TypeError("Expected 'float' type for 'rod_length' attribute.") if value < 0: - raise ValueError( - "Negative value not allowed for 'vertical_offset' attribute." - ) - self._vertical_offset = value + raise ValueError("Negative value not allowed for 'rod_length' attribute.") + self._rod_length = value def _set_plate_bottom_z(self, value: float) -> None: """ @@ -195,19 +217,26 @@ def _set_plate_bottom_z(self, value: float) -> None: raise TypeError("Expected 'float' type for 'plate_bottom_z' attribute.") self._plate_bottom_z = value - def _set_ground_surface_z(self, value: float | None) -> None: + def _set_ground_surface_z(self, value: float) -> None: """ Private setter for ground_surface_z attribute. """ - if value is not None: - if isinstance(value, int): - value = float(value) - if not isinstance(value, float): - raise TypeError( - "Expected 'float' or 'None' type for 'ground_surface_z' attribute." - ) + if isinstance(value, int): + value = float(value) + if not isinstance(value, float): + raise TypeError("Expected 'float' type for 'ground_surface_z' attribute.") self._ground_surface_z = 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_temperature(self, value: float | None) -> None: """ Private setter for temperature attribute. @@ -243,40 +272,38 @@ def _set_comment(self, value: str) -> None: self._comment = value @property - def date_time(self) -> datetime.datetime: + def project(self) -> Project: """ - The date and time of the measurement. + The project which the measurement belongs to. """ - return self._date_time + return self._project @property - def rod_id(self) -> str: + def device(self) -> MeasurementDevice: """ - The ID of the settlement rod. + The measurement device. """ - return self._rod_id + return self._device @property - def point_id(self) -> str: + def object_id(self) -> str: """ - The ID of the measurement point. + The ID of the measured object. """ - return self._point_id + return self._object_id @property - def coordinate_epsg_code(self) -> int: + def date_time(self) -> datetime.datetime: """ - The EPSG code of the coordinate reference system used. - EPSG codes can be found in https://epsg.io/. + The date and time of the measurement. """ - return self._coordinate_epsg_code + return self._date_time @property def coordinate_reference_system(self) -> pyproj.CRS: """ - The coordinate reference system used. + The coordinate reference system of the spatial measurements. It is a `pyproj.CRS` object (see https://pyproj4.github.io/pyproj/stable/api/crs/crs.html). - It is determined based on the `coordinate_epsg_code`. """ return self._coordinate_reference_system @@ -306,13 +333,13 @@ def z(self) -> float: return self._z @property - def vertical_offset(self) -> float: + def rod_length(self) -> float: """ - The vertical offset distance between the measurement point and the bottom of the settlement plate. - It is in principle the rod length plus the plate thickness. + The length of the settlement rod including the thickness of the settlement plate. + It is in principle the vertical distance between the measurement point and the bottom of the settlement plate. Units are according to the `coordinate_reference_system`. """ - return self._vertical_offset + return self._rod_length @property def plate_bottom_z(self) -> float: @@ -330,15 +357,23 @@ def plate_bottom_z_uncorrected(self) -> float: It is computed as the difference beteen the Z-coordinate of the measurement point and the vertical offset. Units are according to the `coordinate_reference_system`. """ - return self.z - self.vertical_offset + return self.z - self.rod_length @property - def ground_surface_z(self) -> float | None: + def ground_surface_z(self) -> float: """ - The Z-coordinate of the ground surface, or None if unknown. + The Z-coordinate of the ground surface. + It is in principle the top of the fill, if present. """ return self._ground_surface_z + @property + def status(self) -> SettlementRodMeasurementStatus: + """ + The status of the measurement. + """ + return self._status + @property def temperature(self) -> float | None: """ @@ -356,6 +391,6 @@ def voltage(self) -> float | None: @property def comment(self) -> str: """ - Comment about the measurement. + Additional comment about the measurement. """ return self._comment diff --git a/src/baec/project.py b/src/baec/project.py new file mode 100644 index 0000000..0336834 --- /dev/null +++ b/src/baec/project.py @@ -0,0 +1,87 @@ +from __future__ import annotations + + +class Project: + """ + Represents a project. + """ + + def __init__( + self, + id_: str, + name: str, + ) -> None: + """ + Initializes a MeasurementDevice object. + + Parameters + ---------- + id_ : str + The ID of the project. + name : str + The name of the project. + + Raises + ------ + TypeError + If the input types are incorrect. + ValueError + If empty string for `id_` or `name`. + """ + + # Initialize all attributes using private setters. + self._set_id(id_) + self._set_name(name) + + def _set_id(self, value: str) -> None: + """ + Private setter for id attribute. + """ + if not isinstance(value, str): + raise TypeError("Expected 'str' type for 'id' attribute.") + if value == "": + raise ValueError("Empty string not allowed for 'id' attribute.") + self._id = value + + def _set_name(self, value: str) -> None: + """ + Private setter for name attribute. + """ + if not isinstance(value, str): + raise TypeError("Expected 'str' type for 'name' attribute.") + if value == "": + raise ValueError("Empty string not allowed for 'name' attribute.") + self._name = value + + @property + def id(self) -> str: + """ + The ID of the project. + """ + return self._id + + @property + def name(self) -> str: + """ + The name of the project. + """ + return self._name + + def __eq__(self, other: object) -> bool: + """ + Check if two MeasurementDevice objects are equal. + It compares the `id` attribute and `name` attribute. + + Parameters + ---------- + other : object + The object to compare. + + Returns + ------- + bool + True if the objects are equal, False otherwise. + """ + if not isinstance(other, Project): + return False + return self.id == other.id and self.name == other.name diff --git a/tests/measurements/test_measurement_device.py b/tests/measurements/test_measurement_device.py new file mode 100644 index 0000000..9c4d85c --- /dev/null +++ b/tests/measurements/test_measurement_device.py @@ -0,0 +1,55 @@ +import pytest + +from baec.measurements.measurement_device import MeasurementDevice + + +def test_measurement_device_with_valid_input() -> None: + """Test initialization of MeasurementDevice with valid input.""" + # With QR code + device = MeasurementDevice(id_="device_1", qr_code="qr_code_1") + assert device.id == "device_1" + assert device.qr_code == "qr_code_1" + + # Without QR code + device = MeasurementDevice(id_="device_1") + assert device.id == "device_1" + assert device.qr_code is None + + +def test_measurement_device_init_with_invalid_id() -> None: + """Test initialization of MeasurementDevice with invalid ID.""" + # Invalid id: None + with pytest.raises(TypeError, match="id"): + MeasurementDevice(id_=None) + + # Invalid id: Empty string + with pytest.raises(ValueError, match="id"): + MeasurementDevice(id_="") + + +def test_measurement_device_init_with_invalid_qr_code() -> None: + """Test initialization of MeasurementDevice with invalid QR-code.""" + # Invalid id: Integer value + with pytest.raises(TypeError, match="qr_code"): + MeasurementDevice(id_="device_1", qr_code=1) + + # Invalid id: Empty string + with pytest.raises(ValueError, match="qr_code"): + MeasurementDevice(id_="device_1", qr_code="") + + +def test_measurement_device__eq__method() -> None: + """Test the __eq__ method of MeasurementDevice.""" + device_1 = MeasurementDevice(id_="device_1", qr_code="qr_code_1") + device_2 = MeasurementDevice(id_="device_1", qr_code="qr_code_1") + device_3 = MeasurementDevice(id_="device_2", qr_code="qr_code_2") + device_4 = MeasurementDevice(id_="device_2", qr_code="qr_code_1") + + assert device_1 == device_2 + assert device_1 != device_3 + assert device_1 != device_4 + assert device_2 != device_3 + assert device_3 == device_4 + + assert device_1 == device_1 + assert device_1 != "device_1" diff --git a/tests/measurements/test_settlement_rod_measurements.py b/tests/measurements/test_settlement_rod_measurement.py similarity index 53% rename from tests/measurements/test_settlement_rod_measurements.py rename to tests/measurements/test_settlement_rod_measurement.py index fd1e654..455caa2 100644 --- a/tests/measurements/test_settlement_rod_measurements.py +++ b/tests/measurements/test_settlement_rod_measurement.py @@ -3,255 +3,269 @@ import pyproj import pytest -from baec.measurements.settlement_rod_measurements import SettlementRodMeasurement +from baec.measurements.measurement_device import MeasurementDevice +from baec.measurements.settlement_rod_measurement import ( + SettlementRodMeasurement, + SettlementRodMeasurementStatus, +) +from baec.project import Project def test_settlement_rod_measurement_init_with_valid_input() -> None: """Test initialization of settlement rod measurement with valid input.""" + project = Project(id_="P-001", name="Project 1") + device = MeasurementDevice(id_="BR_003", qr_code="QR-003") + object_id = "ZB-02" date_time = datetime.datetime(2024, 4, 9, 4, 0, 0) - rod_id = "BR_003" - point_id = "ZB-02" - coordinate_epsg_code = 28992 + coordinate_reference_system = pyproj.CRS.from_user_input(28992) x = 123340.266 y = 487597.154 z = 0.807 - vertical_offset = 2.0 - ground_surface_z = 0.419 + rod_length = 2.0 plate_bottom_z = -1.193 - plate_bottom_z_uncorrected = -1.193 + ground_surface_z = 0.419 + status = SettlementRodMeasurementStatus.OK temperature = 12.0 voltage = 4232 comment = "No comment" - coordinate_reference_system = pyproj.CRS.from_user_input(coordinate_epsg_code) + plate_bottom_z_uncorrected = -1.193 measurement = SettlementRodMeasurement( + project=project, + device=device, + object_id=object_id, date_time=date_time, - rod_id=rod_id, - point_id=point_id, - coordinate_epsg_code=coordinate_epsg_code, + coordinate_reference_system=coordinate_reference_system, x=x, y=y, z=z, - vertical_offset=vertical_offset, - ground_surface_z=ground_surface_z, + rod_length=rod_length, plate_bottom_z=plate_bottom_z, + ground_surface_z=ground_surface_z, + status=status, temperature=temperature, voltage=voltage, comment=comment, ) + assert measurement.project == project + assert measurement.device == device + assert measurement.object_id == object_id assert measurement.date_time == date_time - assert measurement.rod_id == rod_id - assert measurement.point_id == point_id - assert measurement.coordinate_epsg_code == coordinate_epsg_code + assert measurement.coordinate_reference_system == coordinate_reference_system assert measurement.x == x assert measurement.y == y assert measurement.z == z - assert measurement.vertical_offset == vertical_offset + assert measurement.rod_length == rod_length assert measurement.ground_surface_z == ground_surface_z assert measurement.plate_bottom_z == plate_bottom_z + assert measurement.status == status assert measurement.plate_bottom_z_uncorrected == plate_bottom_z_uncorrected assert measurement.temperature == temperature assert measurement.voltage == voltage assert measurement.comment == comment - assert measurement.coordinate_reference_system == coordinate_reference_system -def test_settlement_rod_measurement_init_with_invalid_date_time() -> None: - """Test initialization of settlement rod measurement with invalid date_time.""" - # Invalid date_time: None - with pytest.raises(TypeError): +def test_settlement_rod_measurement_init_with_invalid_project() -> None: + """Test initialization of settlement rod measurement with invalid project.""" + # Invalid project: None + with pytest.raises(TypeError, match="project"): SettlementRodMeasurement( - date_time=None, - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + project=None, + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", + date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) -def test_settlement_rod_measurement_init_with_invalid_rod_id() -> None: - """Test initialization of settlement rod measurement with invalid rod_id.""" - # Invalid rod_id: None - with pytest.raises(TypeError): +def test_settlement_rod_measurement_init_with_invalid_device() -> None: + """Test initialization of settlement rod measurement with invalid device.""" + # Invalid device: None + with pytest.raises(TypeError, match="device"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=None, + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id=None, - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, - temperature=12.0, - voltage=4232, - comment="No comment", - ) - - # Invalid rod_id: Empty string - with pytest.raises(ValueError): - SettlementRodMeasurement( - date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="", - point_id="ZB-02", - coordinate_epsg_code=28992, - x=123340.266, - y=487597.154, - z=0.807, - vertical_offset=2.0, ground_surface_z=0.419, - plate_bottom_z=-1.193, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) -def test_settlement_rod_measurement_init_with_invalid_point_id() -> None: - """Test initialization of settlement rod measurement with invalid point_id.""" +def test_settlement_rod_measurement_init_with_invalid_object_id() -> None: + """Test initialization of settlement rod measurement with invalid object_id.""" # Invalid point_id: None - with pytest.raises(TypeError): + with pytest.raises(TypeError, match="object_id"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id=None, date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id=None, - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) # Invalid rod_id: Empty string - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="object_id"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) -def test_settlement_rod_measurement_init_with_invalid_coordinate_epsg_code() -> None: - """Test initialization of settlement rod measurement with invalid coordinate_epsg_code.""" - # Invalid coordinate_epsg_code: None - with pytest.raises(TypeError): +def test_settlement_rod_measurement_init_with_invalid_date_time() -> None: + """Test initialization of settlement rod measurement with invalid date_time.""" + # Invalid date_time: None + with pytest.raises(TypeError, match="date_time"): SettlementRodMeasurement( - date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=None, + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", + date_time=None, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) - # Invalid coordinate_epsg_code: Negative value - with pytest.raises(ValueError): + +def test_settlement_rod_measurement_init_with_invalid_coordinate_reference_system() -> ( + None +): + """Test initialization of settlement rod measurement with invalid coordinate reference system.""" + # Invalid coordinate_reference_system: None + with pytest.raises(TypeError, match="coordinate_reference_system"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=-28992, + coordinate_reference_system=None, x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) -def test_settlement_rod_measurement_init_with_invalid_point_x() -> None: - """Test initialization of settlement rod measurement with invalid point_x.""" - # Invalid point_x: String value - with pytest.raises(TypeError): +def test_settlement_rod_measurement_init_with_invalid_x() -> None: + """Test initialization of settlement rod measurement with invalid x.""" + # Invalid x: String value + with pytest.raises(TypeError, match="x"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x="123340.266", y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) -def test_settlement_rod_measurement_init_with_invalid_point_y() -> None: - """Test initialization of settlement rod measurement with invalid point_y.""" - # Invalid point_y: None - with pytest.raises(TypeError): +def test_settlement_rod_measurement_init_with_invalid_y() -> None: + """Test initialization of settlement rod measurement with invalid y.""" + # Invalid y: None + with pytest.raises(TypeError, match="y"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=None, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) -def test_settlement_rod_measurement_init_with_invalid_point_z() -> None: - """Test initialization of settlement rod measurement with invalid point_z.""" - # Invalid point_z: String value - with pytest.raises(TypeError): +def test_settlement_rod_measurement_init_with_z() -> None: + """Test initialization of settlement rod measurement with invalid z.""" + # Invalid z: String value + with pytest.raises(TypeError, match="z"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z="0.807", - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", @@ -261,36 +275,40 @@ def test_settlement_rod_measurement_init_with_invalid_point_z() -> None: def test_settlement_rod_measurement_init_with_invalid_rod_length() -> None: """Test initialization of settlement rod measurement with invalid rod_length.""" # Invalid point_z: String value - with pytest.raises(TypeError): + with pytest.raises(TypeError, match="rod_length"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset="2.0", - ground_surface_z=0.419, + rod_length="2.0", plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) # Invalid rod_length: Negative value - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="rod_length"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=-2.0, - ground_surface_z=0.419, + rod_length=-2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", @@ -300,39 +318,43 @@ def test_settlement_rod_measurement_init_with_invalid_rod_length() -> None: def test_settlement_rod_measurement_init_with_invalid_ground_surface_z() -> None: """Test initialization of settlement rod measurement with invalid ground_surface_z.""" # Invalid ground_surface_z: String value - with pytest.raises(TypeError): + with pytest.raises(TypeError, match="ground_surface_z"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z="-0.419", + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z="0.419", + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", ) -def test_settlement_rod_measurement_init_with_invalid_ground_plate_z() -> None: - """Test initialization of settlement rod measurement with invalid ground_plate_z.""" - # Invalid ground_plate_z: String value - with pytest.raises(TypeError): +def test_settlement_rod_measurement_init_with_invalid_plate_bottom_z() -> None: + """Test initialization of settlement rod measurement with invalid plate_bottom_z.""" + # Invalid plate_bottom_z: String value + with pytest.raises(TypeError, match="plate_bottom_z"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, + rod_length=2.0, + plate_bottom_z="-1.193", ground_surface_z=0.419, - plate_bottom_z="1.193", + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, comment="No comment", @@ -342,18 +364,20 @@ def test_settlement_rod_measurement_init_with_invalid_ground_plate_z() -> None: def test_settlement_rod_measurement_init_with_invalid_temperature() -> None: """Test initialization of settlement rod measurement with invalid temperature.""" # Invalid temperature: String value - with pytest.raises(TypeError): + with pytest.raises(TypeError, match="temperature"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature="12.0", voltage=4232, comment="No comment", @@ -363,18 +387,20 @@ def test_settlement_rod_measurement_init_with_invalid_temperature() -> None: def test_settlement_rod_measurement_init_with_invalid_voltage() -> None: """Test initialization of settlement rod measurement with invalid voltage.""" # Invalid voltage: String value - with pytest.raises(TypeError): + with pytest.raises(TypeError, match="voltage"): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, + ground_surface_z=0.419, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage="4232", comment="No comment", @@ -386,39 +412,19 @@ def test_settlement_rod_measurement_init_with_invalid_comment() -> None: # Invalid comment: Integer value with pytest.raises(TypeError): SettlementRodMeasurement( + project=Project(id_="P-001", name="Project 1"), + device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), + object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992, + coordinate_reference_system=pyproj.CRS.from_user_input(28992), x=123340.266, y=487597.154, z=0.807, - vertical_offset=2.0, - ground_surface_z=0.419, + rod_length=2.0, plate_bottom_z=-1.193, - temperature=12.0, - voltage=4232, - comment=123, - ) - - -def test_settlement_rod_measurement_init_with_invalid_coordinate_reference_system() -> ( - None -): - """Test initialization of settlement rod measurement with invalid coordinate reference system.""" - with pytest.raises(pyproj.exceptions.CRSError): - SettlementRodMeasurement( - date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - rod_id="BR_003", - point_id="ZB-02", - coordinate_epsg_code=28992111, # no coordinate system corresponds to this epsg code. - x=123340.266, - y=487597.154, - z=0.807, - vertical_offset=2.0, ground_surface_z=0.419, - plate_bottom_z=-1.193, + status=SettlementRodMeasurementStatus.OK, temperature=12.0, voltage=4232, - comment="No comment", + comment=123, ) diff --git a/tests/test_project.py b/tests/test_project.py new file mode 100644 index 0000000..9e67ec7 --- /dev/null +++ b/tests/test_project.py @@ -0,0 +1,50 @@ +import pytest + +from baec.project import Project + + +def test_project_with_valid_input() -> None: + """Test initialization of Project with valid input.""" + project = Project(id_="P001", name="name_1") + assert project.id == "P001" + assert project.name == "name_1" + + +def test_project_init_with_invalid_id() -> None: + """Test initialization of Project with invalid ID.""" + # Invalid id: None + with pytest.raises(TypeError, match="id"): + Project(id_=None, name="name_1") + + # Invalid id: Empty string + with pytest.raises(ValueError, match="id"): + Project(id_="", name="name_1") + + +def test_project_init_with_invalid_name() -> None: + """Test initialization of Project with invalid name.""" + # Invalid name: None + with pytest.raises(TypeError, match="name"): + Project(id_="P001", name=None) + + # Invalid name: Empty string + with pytest.raises(ValueError, match="name"): + Project(id_="P001", name="") + + +def test_project__eq__method() -> None: + """Test the __eq__ method of Project.""" + project_1 = Project(id_="P001", name="name_1") + project_2 = Project(id_="P001", name="name_1") + project_3 = Project(id_="P002", name="name_2") + project_4 = Project(id_="P002", name="name_1") + + assert project_1 == project_2 + assert project_1 != project_3 + assert project_1 != project_4 + assert project_2 != project_3 + assert project_3 != project_4 + + assert project_1 == project_1 + assert project_1 != None + assert project_1 != "P001" From 75d4e1e3c9291da12e5a93f14009dfda1f3fc2b8 Mon Sep 17 00:00:00 2001 From: Pablo Vasconez Date: Thu, 6 Jun 2024 16:54:56 +0200 Subject: [PATCH 4/7] feat(settlement_rod_measurement_series): add SettlementRodMeasurementSeries class to represent a series of measurements for a single settlement rod and update docs --- docs/conf.py | 1 + docs/tree/figures/settlement_rod.png | Bin 40356 -> 39160 bytes docs/tree/reference.rst | 49 ++++++- .../settlement_rod_measurement_series.py | 137 ++++++++++++++++++ tests/measurements/conftest.py | 57 ++++++++ .../test_settlement_rod_measurement_series.py | 115 +++++++++++++++ 6 files changed, 355 insertions(+), 4 deletions(-) create mode 100644 src/baec/measurements/settlement_rod_measurement_series.py create mode 100644 tests/measurements/conftest.py create mode 100644 tests/measurements/test_settlement_rod_measurement_series.py diff --git a/docs/conf.py b/docs/conf.py index faa0f36..13c05ba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,6 +37,7 @@ "sphinx.ext.mathjax", "sphinx_autodoc_typehints", "matplotlib.sphinxext.plot_directive", + "enum_tools.autoenum", ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/tree/figures/settlement_rod.png b/docs/tree/figures/settlement_rod.png index d6ccb4aad090faaa2643c586b0c90252129ecf2d..6702a751a532aff294d66e0be983b042cc38be75 100644 GIT binary patch literal 39160 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 mz1~bgspua#?*H_Zb@dt?zHv~Lw9;>Z(;m*?W_H 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 diff --git a/docs/tree/reference.rst b/docs/tree/reference.rst index 82133e6..7ce2f38 100644 --- a/docs/tree/reference.rst +++ b/docs/tree/reference.rst @@ -4,21 +4,62 @@ Reference ========= .. _settlementRodMeasurement: + Settlement Rod Measurement ---------------------------- -A settlement rod device consists of a rod and a (bottom) settlement plate (see figure below). The measurements are taken at the top of the rod (i.e. the `measurement point`) -and since the `vertical offset` between the measurement point is known, the settlement of at the bottom of the plate `plate bottom z` can be also derived. Optionally, -the `ground surface z` can be measured by performing for instance radar measurements. +A settlement rod device consists of a rod and a (bottom) settlement plate (see figure below). The measurements are taken at the top of the rod (i.e. the `measurement_point`) +and at the `ground surface` and since the `rod length` is known, the settlement of at the bottom of the plate (i.e. `plate_bottom_z`) can be also derived. .. image:: figures/settlement_rod.png The class `SettlementRodMeasurement` presented below stores the data of a single settlement rod measurement. -.. autoclass:: baec.measurements.settlement_rod_measurements.SettlementRodMeasurement +.. autoclass:: baec.measurements.settlement_rod_measurement.SettlementRodMeasurement :members: :inherited-members: :member-order: bysource .. automethod:: __init__ +.. autoenum:: baec.measurements.settlement_rod_measurement.SettlementRodMeasurementStatus + :members: + + +.. _settlementRodMeasurementSeries: + +Settlement Rod Measurement Series +--------------------------------- + +.. autoclass:: baec.measurements.settlement_rod_measurement_series.SettlementRodMeasurementSeries + :members: + :inherited-members: + :member-order: bysource + + .. automethod:: __init__ + + +.. _measurementDevice: + +Measurement Device +------------------ + +.. autoclass:: baec.measurements.measurement_device.MeasurementDevice + :members: + :inherited-members: + :member-order: bysource + + .. automethod:: __init__ + +.. _measurementProject: + +Project +------- + +.. autoclass:: baec.project.Project + :members: + :inherited-members: + :member-order: bysource + + .. automethod:: __init__ + diff --git a/src/baec/measurements/settlement_rod_measurement_series.py b/src/baec/measurements/settlement_rod_measurement_series.py new file mode 100644 index 0000000..61c2514 --- /dev/null +++ b/src/baec/measurements/settlement_rod_measurement_series.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +from typing import List + +import pandas as pd + +from baec.measurements.settlement_rod_measurement import SettlementRodMeasurement + + +class SettlementRodMeasurementSeries: + """ + Represents a series of measurements for a single settlement rod. + """ + + def __init__(self, measurements: List[SettlementRodMeasurement]) -> None: + """ + Initializes a SettlementRodMeasurementSeries object. + + Parameters + ---------- + measurements : List[SettlementRodMeasurement] + The list of measurements for the settlement rod. + + Raises + ------ + TypeError + If the input types are incorrect. + ValueError + If the list of measurements is empty. + If the measurements are not for the same project, device or object. + """ + + # Initialize all attributes using private setters. + self._set_measurements(measurements) + + def _set_measurements(self, value: List[SettlementRodMeasurement]) -> None: + """Private setter for measurements attribute.""" + + # Check if the input is a list of SettlementRodMeasurement objects. + if not all(isinstance(item, SettlementRodMeasurement) for item in value): + raise TypeError( + "Expected 'List[SettlementRodMeasurement]' type for 'measurements' attribute." + ) + + # Check if the list is not empty. + if not value: + raise ValueError("Empty list not allowed for 'measurements' attribute.") + + # Check that the measurements are for the same project. + projects = [] + for measurement in value: + if measurement.project not in projects: + projects.append(measurement.project) + if len(projects) > 1: + raise ValueError("All measurements must be for the same project.") + + # Check that the measurements are for the same device. + measurement_devices = [] + for measurement in value: + if measurement.device not in measurement_devices: + measurement_devices.append(measurement.device) + if len(measurement_devices) > 1: + raise ValueError("All measurements must be for the same device.") + + # Check that the measurements are for the same object. + object_ids = [] + for measurement in value: + if measurement.object_id not in object_ids: + object_ids.append(measurement.object_id) + if len(object_ids) > 1: + raise ValueError("All measurements must be for the same measured object.") + + # Organize the list of measurements in chronological order. + self._measurements = sorted(value, key=lambda x: x.date_time) + + @property + def measurements(self) -> List[SettlementRodMeasurement]: + """ + The list of measurements for the settlement rod. + They are organized in chronological order. + """ + return self._measurements + + def to_dataframe(self) -> pd.DataFrame: + """ + Convert the series of measurements to a pandas DataFrame. + + Returns + ------- + pd.DataFrame + A pandas DataFrame with the measurements. The columns of the DataFrame are: + project_id, project_name, device_id, device_qr_code, object_id, + coordinate_epsg_code, date_time, x, y, z, rod_length, plate_bottom_z + plate_bottom_z_uncorrected, ground_surface_z, status, temperature, + voltage, comment. + """ + # Initialize an empty DataFrame. + df = pd.DataFrame() + + # Add columns to the DataFrame. + df["project_id"] = [measurement.project.id for measurement in self.measurements] + df["project_name"] = [ + measurement.project.name for measurement in self.measurements + ] + df["device_id"] = [measurement.device.id for measurement in self.measurements] + df["device_qr_code"] = [ + measurement.device.qr_code for measurement in self.measurements + ] + df["object_id"] = [measurement.object_id for measurement in self.measurements] + df["coordinate_epsg_code"] = [ + measurement.coordinate_reference_system.to_epsg() + for measurement in self.measurements + ] + df["date_time"] = pd.to_datetime( + [measurement.date_time for measurement in self.measurements] + ) + df["x"] = [measurement.x for measurement in self.measurements] + df["y"] = [measurement.y for measurement in self.measurements] + df["z"] = [measurement.z for measurement in self.measurements] + df["rod_length"] = [measurement.rod_length for measurement in self.measurements] + df["plate_bottom_z"] = [ + measurement.plate_bottom_z for measurement in self.measurements + ] + df["plate_bottom_z_uncorrected"] = [ + measurement.plate_bottom_z_uncorrected for measurement in self.measurements + ] + df["ground_surface_z"] = [ + measurement.ground_surface_z for measurement in self.measurements + ] + df["status"] = [measurement.status.value for measurement in self.measurements] + df["temperature"] = [ + measurement.temperature for measurement in self.measurements + ] + df["voltage"] = [measurement.voltage for measurement in self.measurements] + df["comment"] = [measurement.comment for measurement in self.measurements] + + return df diff --git a/tests/measurements/conftest.py b/tests/measurements/conftest.py new file mode 100644 index 0000000..ddc71dc --- /dev/null +++ b/tests/measurements/conftest.py @@ -0,0 +1,57 @@ +import datetime +from typing import List + +import pyproj +import pytest + +from baec.measurements.measurement_device import MeasurementDevice +from baec.measurements.settlement_rod_measurement import ( + SettlementRodMeasurement, + SettlementRodMeasurementStatus, +) +from baec.project import Project + + +@pytest.fixture +def example_settlement_rod_measurements() -> List[SettlementRodMeasurement]: + project = Project(id_="P-001", name="Project 1") + device = MeasurementDevice(id_="BR_003", qr_code="QR-003") + object_id = "ZB-02" + date_time_start = datetime.datetime(2024, 4, 9, 4, 0, 0) + coordinate_reference_system = pyproj.CRS.from_user_input(28992) + x = 123340.266 + y = 487597.154 + z_start = 0.807 + rod_length = 2.0 + plate_bottom_z = -1.193 + ground_surface_z = 0.419 + status = SettlementRodMeasurementStatus.OK + temperature = 12.0 + voltage = 4232 + comment = "No comment" + + measurements = [] + for i in range(10): + z = z_start - 0.01 * i + date_time = date_time_start + datetime.timedelta(days=i) + measurements.append( + SettlementRodMeasurement( + project=project, + device=device, + object_id=object_id, + date_time=date_time, + coordinate_reference_system=coordinate_reference_system, + x=x, + y=y, + z=z, + rod_length=rod_length, + plate_bottom_z=plate_bottom_z, + ground_surface_z=ground_surface_z, + status=status, + temperature=temperature, + voltage=voltage, + comment=comment, + ) + ) + + return measurements diff --git a/tests/measurements/test_settlement_rod_measurement_series.py b/tests/measurements/test_settlement_rod_measurement_series.py new file mode 100644 index 0000000..89722f6 --- /dev/null +++ b/tests/measurements/test_settlement_rod_measurement_series.py @@ -0,0 +1,115 @@ +import datetime +from copy import deepcopy +from typing import List + +import pyproj +import pytest + +from baec.measurements.measurement_device import MeasurementDevice +from baec.measurements.settlement_rod_measurement import SettlementRodMeasurement +from baec.measurements.settlement_rod_measurement_series import ( + SettlementRodMeasurementSeries, +) +from baec.project import Project + + +def test_settlement_rod_measurement_series_init_with_valid_input( + example_settlement_rod_measurements: List[SettlementRodMeasurement], +) -> None: + """Test initialization of SettlementRodMeasurementSeries with valid input.""" + + # Create series from measurements in chronological order. + series = SettlementRodMeasurementSeries( + measurements=example_settlement_rod_measurements + ) + + assert series.measurements == example_settlement_rod_measurements + + # Check that the measurements are in chronological order. + assert sorted(series.measurements, key=lambda x: x.date_time) == series.measurements + + # Create series from measurements in inverse chronological order. + series = SettlementRodMeasurementSeries( + measurements=example_settlement_rod_measurements[::-1] + ) + + assert series.measurements == example_settlement_rod_measurements + + # Check that the measurements are in chronological order. + assert sorted(series.measurements, key=lambda x: x.date_time) == series.measurements + + +def test_settlement_rod_measurement_series_init_with_invalid_measurements( + example_settlement_rod_measurements: List[SettlementRodMeasurement], +) -> None: + """Test initialization of SettlementRodMeasurementSeries with invalid measurements.""" + + # Empty list + with pytest.raises(ValueError, match="measurements"): + SettlementRodMeasurementSeries(measurements=[]) + + # Incorrect type: One item is a string. + measurements = deepcopy(example_settlement_rod_measurements) + measurements[0] = "invalid" + with pytest.raises(TypeError): + SettlementRodMeasurementSeries(measurements=measurements) + + # Different projects + measurements = deepcopy(example_settlement_rod_measurements) + measurements[0]._project = Project(id_="P-002", name="Project 2") + + with pytest.raises(ValueError, match="project"): + SettlementRodMeasurementSeries(measurements=measurements) + + # Different devices + measurements = deepcopy(example_settlement_rod_measurements) + measurements[0]._device = MeasurementDevice(id_="BR_004", qr_code="QR-004") + + with pytest.raises(ValueError, match="device"): + SettlementRodMeasurementSeries(measurements=measurements) + + # Different objects + measurements = deepcopy(example_settlement_rod_measurements) + measurements[0]._object_id = "ZB-20" + + with pytest.raises(ValueError, match="object"): + SettlementRodMeasurementSeries(measurements=measurements) + + +def test_settlement_rod_measurement_series_to_dataframe_method( + example_settlement_rod_measurements: List[SettlementRodMeasurement], +) -> None: + """Test the to_dataframe method of SettlementRodMeasurementSeries.""" + series = SettlementRodMeasurementSeries( + measurements=example_settlement_rod_measurements + ) + + df = series.to_dataframe() + + # Check that the DataFrame has the correct number of rows. + assert len(df) == len(example_settlement_rod_measurements) + + # Check that the DataFrame has the correct data. + for i, measurement in enumerate(example_settlement_rod_measurements): + assert df.iloc[i]["project_id"] == measurement.project.id + assert df.iloc[i]["device_id"] == measurement.device.id + assert df.iloc[i]["object_id"] == measurement.object_id + assert ( + df.iloc[i]["coordinate_epsg_code"] + == measurement.coordinate_reference_system.to_epsg() + ) + assert df.iloc[i]["date_time"] == measurement.date_time + assert df.iloc[i]["x"] == measurement.x + assert df.iloc[i]["y"] == measurement.y + assert df.iloc[i]["z"] == measurement.z + assert df.iloc[i]["rod_length"] == measurement.rod_length + assert df.iloc[i]["plate_bottom_z"] == measurement.plate_bottom_z + assert ( + df.iloc[i]["plate_bottom_z_uncorrected"] + == measurement.plate_bottom_z_uncorrected + ) + assert df.iloc[i]["ground_surface_z"] == measurement.ground_surface_z + assert df.iloc[i]["status"] == measurement.status.value + assert df.iloc[i]["temperature"] == measurement.temperature + assert df.iloc[i]["voltage"] == measurement.voltage + assert df.iloc[i]["comment"] == measurement.comment From bcf86085d26cd6a67bf81a5e1285aa2dd0f28cee Mon Sep 17 00:00:00 2001 From: Pablo Vasconez Date: Mon, 10 Jun 2024 11:51:22 +0200 Subject: [PATCH 5/7] apply(#18): add plots to SettlementRodMeasurementSeries, add CoordinateReferenceSystems class and refactor attribute names in SettlementRodMeasurement as discussed in comments of PR #18 --- docs/tree/figures/settlement_rod.png | Bin 39160 -> 36268 bytes docs/tree/reference.rst | 4 +- src/baec/coordinates.py | 174 ++++++++++ .../settlement_rod_measurement.py | 163 +++++---- .../settlement_rod_measurement_series.py | 309 +++++++++++++++--- tests/measurements/conftest.py | 24 +- .../test_settlement_rod_measurement.py | 261 ++++++++------- .../test_settlement_rod_measurement_series.py | 173 +++++++++- tests/test_coordinates.py | 88 +++++ 9 files changed, 947 insertions(+), 249 deletions(-) create mode 100644 src/baec/coordinates.py create mode 100644 tests/test_coordinates.py diff --git a/docs/tree/figures/settlement_rod.png b/docs/tree/figures/settlement_rod.png index 6702a751a532aff294d66e0be983b042cc38be75..df2e79e5762ec5f5423ab033c0f64f0fddeeef64 100644 GIT binary patch literal 36268 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
Hv&ix6AvGY5Uajc&Q60ou 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 mz1~bgspua#?*H_Zb@dt?zHv~Lw9;>Z None: + """ + Initializes a CoordinateReferenceSystems object. + + Parameters: + ---------- + horizontal : pyproj.CRS + The coordinate reference system of the X and Y-coordinates. + It is a `pyproj.CRS` object (see https://pyproj4.github.io/pyproj/stable/api/crs/crs.html) of + type 'Projected' or 'Compound'. + vertical : pyproj.CRS + The coordinate reference system of the Z-coordinate. + It is a `pyproj.CRS` object (see https://pyproj4.github.io/pyproj/stable/api/crs/crs.html) of + type 'Vertical' or 'Compound'. + + Raises + ------ + TypeError + If the input types are incorrect. + ValueError + If `horizontal` is not a projected or compound CRS. + If `vertical` is not a vertical or compound CRS. + """ + + # Initialize all attributes using private setters. + self._set_horizontal(horizontal) + self._set_vertical(vertical) + + @classmethod + def from_epsg(cls, horizontal: int, vertical: int) -> CoordinateReferenceSystems: + """ + Creates a CoordinateReferenceSystems object from the EPSG codes of the horizontal and vertical CRS. + + Parameters: + ---------- + horizontal : int + The EPSG code of the horizontal CRS. + vertical : int + The EPSG code of the vertical CRS. + + Returns: + ------- + CoordinateReferenceSystems + A CoordinateReferenceSystems object with the horizontal and vertical CRS. + + Raises + ------ + pyproj.exceptions.CRSError + If the EPSG codes are not valid. + """ + return cls( + horizontal=pyproj.CRS.from_epsg(horizontal), + vertical=pyproj.CRS.from_epsg(vertical), + ) + + def _set_horizontal(self, value: pyproj.CRS) -> None: + """ + Private setter for horizontal attribute. + """ + if not isinstance(value, pyproj.CRS): + raise TypeError("Expected 'pyproj.CRS' type for 'horizontal' attribute.") + + # Check whether the CRS is a projected or compound CRS. + if not value.is_projected and not value.is_compound: + raise ValueError( + "Expected 'is_projected' or 'is_compound' to be true for 'horizontal' attribute." + ) + + # Set the coordinate system in case of a projected CRS. + if value.is_projected: + self._horizontal = value + # Set the coordinate system in case of a compound CRS. + elif value.is_compound: + projected_crs = None + for crs in value.sub_crs_list: + if crs.is_projected: + projected_crs = crs + break + if projected_crs is None: + raise ValueError("No projected CRS found in the compound CRS.") + self._horizontal = projected_crs + + def _set_vertical(self, value: pyproj.CRS) -> None: + """ + Private setter for z attribute. + """ + if not isinstance(value, pyproj.CRS): + raise TypeError("Expected 'pyproj.CRS' type for 'vertical' attribute.") + + # Check whether the CRS is a vertical or compound CRS. + if not value.is_vertical and not value.is_compound: + raise ValueError( + "Expected 'is_vertical' or 'is_compound' to be true for 'vertical' attribute." + ) + + # Set the coordinate system in case of a vertical CRS. + if value.is_vertical: + self._vertical = value + # Set the coordinate system in case of a compound CRS. + elif value.is_compound: + vertical_crs = None + for crs in value.sub_crs_list: + if crs.is_vertical: + vertical_crs = crs + break + if vertical_crs is None: + raise ValueError("No vertical CRS found in the compound CRS.") + self._vertical = vertical_crs + + @property + def horizontal(self) -> pyproj.CRS: + """ + The coordinate reference system of the horizontal X and Y-coordinates. + """ + return self._horizontal + + @property + def vertical(self) -> pyproj.CRS: + """ + The coordinate reference system of the vertical Z-coordinate. + """ + return self._vertical + + @property + def horizontal_units(self) -> str: + """ + The units of the horizontal CRS. + """ + return self._horizontal.axis_info[0].unit_name + + @property + def vertical_units(self) -> str: + """ + The units of the vertical CRS + """ + return self._vertical.axis_info[0].unit_name + + @property + def vertical_datum(self) -> str: + """ + The name of the vertical datum. + """ + return self._vertical.name + + @cached_property + def vertical_datum_and_units(self) -> str: + """ + The vertical datum and units of the vertical CRS. + """ + return f"{self.vertical_datum} [{self.vertical_units}]" + + def __str__(self) -> str: + """Converts the object to a string.""" + return f"CoordinateReferenceSystems(Horizontal: {self.horizontal.to_epsg()}, Vertical: {self.vertical.to_epsg()})" + + def __eq__(self, value: object) -> bool: + """Compares two CoordinateReferenceSystems objects.""" + if not isinstance(value, CoordinateReferenceSystems): + return False + return self.horizontal.equals(value.horizontal) and self.vertical.equals( + value.vertical + ) diff --git a/src/baec/measurements/settlement_rod_measurement.py b/src/baec/measurements/settlement_rod_measurement.py index e3bc184..77b9afa 100644 --- a/src/baec/measurements/settlement_rod_measurement.py +++ b/src/baec/measurements/settlement_rod_measurement.py @@ -2,9 +2,9 @@ import datetime from enum import Enum +from functools import cache -import pyproj - +from baec.coordinates import CoordinateReferenceSystems from baec.measurements.measurement_device import MeasurementDevice from baec.project import Project @@ -34,12 +34,12 @@ def __init__( device: MeasurementDevice, object_id: str, date_time: datetime.datetime, - coordinate_reference_system: pyproj.CRS, - x: float, - y: float, - z: float, + coordinate_reference_systems: CoordinateReferenceSystems, + rod_top_x: float, + rod_top_y: float, + rod_top_z: float, rod_length: float, - plate_bottom_z: float, + rod_bottom_z: float, ground_surface_z: float, status: SettlementRodMeasurementStatus, temperature: float | None = None, @@ -61,29 +61,32 @@ def __init__( The ID of the measured object. date_time : datetime.datetime The date and time of the measurement. - coordinate_reference_system : pyproj.CRS - The coordinate reference system of the spatial measurements. - It is a `pyproj.CRS` object (see https://pyproj4.github.io/pyproj/stable/api/crs/crs.html). - x : float - The X-coordinate of the measurement point. Units are according to the `coordinate_reference_system`. - y : float - The Y-coordinate of the measurement point. Units are according to the `coordinate_reference_system`. - z : float - The Z-coordinate of the measurement point. + coordinate_reference_systems : CoordinateReferenceSystems + The horizontal (X, Y) and vertical (Z) coordinate reference systems (CRS) of the + spatial measurements. + rod_top_x : float + The horizontal X-coordinate of the top of the settlement rod. + Units are according to the `coordinate_reference_systems`. + rod_top_y : float + The horizontal Y-coordinate of the top of the settlement rod. + Units are according to the `coordinate_reference_systems`. + rod_top_z : float + The vertical Z-coordinate of the top of the settlement rod. It is the top of the settlement rod. - Units are according to the `coordinate_reference_system`. + Units and datum are according to the `coordinate_reference_systems`. rod_length : float The length of the settlement rod including the thickness of the settlement plate. - It is in principle the vertical distance between the measurement point and the bottom of the settlement plate. - Units are according to the `coordinate_reference_system`. - plate_bottom_z : float - The corrected Z-coordinate at the bottom of the settlement plate. + It is in principle the vertical distance between the top of the settlement rod and + the bottom of the settlement plate. + Units are according to the `coordinate_reference_systems`. + rod_bottom_z : float + The corrected Z-coordinate at the bottom of the settlement rod (coincides with bottom of settlement plate). Note that the bottom of the plate is in principle the original ground surface. - Units are according to the `coordinate_reference_system`. + Units and datum according to the `coordinate_reference_systems`. ground_surface_z : float The Z-coordinate of the ground surface. It is in principle the top of the fill, if present. - Units are according to the `coordinate_reference_system`. + Units and datum according to the `coordinate_reference_systems`. status: SettlementRodMeasurementStatus The status of the measurement. temperature : float or None, optional @@ -107,12 +110,12 @@ def __init__( self._set_device(device) self._set_object_id(object_id) self._set_date_time(date_time) - self._set_coordinate_reference_system(coordinate_reference_system) - self._set_x(x) - self._set_y(y) - self._set_z(z) + self._set_coordinate_reference_systems(coordinate_reference_systems) + self._set_rod_top_x(rod_top_x) + self._set_rod_top_y(rod_top_y) + self._set_rod_top_z(rod_top_z) self._set_rod_length(rod_length) - self._set_plate_bottom_z(plate_bottom_z) + self._set_rod_bottom_z(rod_bottom_z) self._set_ground_surface_z(ground_surface_z) self._set_status(status) self._set_temperature(temperature) @@ -155,44 +158,46 @@ def _set_date_time(self, value: datetime.datetime) -> None: ) self._date_time = value - def _set_coordinate_reference_system(self, value: pyproj.CRS) -> None: + def _set_coordinate_reference_systems( + self, value: CoordinateReferenceSystems + ) -> None: """ - Private setter for coordinate_reference_system attribute. + Private setter for coordinate_reference_systems attribute. """ - if not isinstance(value, pyproj.CRS): + if not isinstance(value, CoordinateReferenceSystems): raise TypeError( - "Expected 'pyproj.CRS' type for 'coordinate_reference_system' attribute." + "Expected 'CoordinateReferenceSystems' type for 'coordinate_reference_systems' attribute." ) - self._coordinate_reference_system = value + self._coordinate_reference_systems = value - def _set_x(self, value: float) -> None: + def _set_rod_top_x(self, value: float) -> None: """ - Private setter for x attribute. + Private setter for rod_top_x attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'x' attribute.") + raise TypeError("Expected 'float' type for 'rod_top_x' attribute.") self._x = value - def _set_y(self, value: float) -> None: + def _set_rod_top_y(self, value: float) -> None: """ - Private setter for y attribute. + Private setter for rod_top_y attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type 'y' attribute.") + raise TypeError("Expected 'float' type 'rod_top_y' attribute.") self._y = value - def _set_z(self, value: float) -> None: + def _set_rod_top_z(self, value: float) -> None: """ - Private setter for z attribute. + Private setter for rod_top_z attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'z' attribute.") + raise TypeError("Expected 'float' type for 'rod_top_z' attribute.") self._z = value def _set_rod_length(self, value: float) -> None: @@ -207,15 +212,15 @@ def _set_rod_length(self, value: float) -> None: raise ValueError("Negative value not allowed for 'rod_length' attribute.") self._rod_length = value - def _set_plate_bottom_z(self, value: float) -> None: + def _set_rod_bottom_z(self, value: float) -> None: """ - Private setter for plate_bottom_z attribute. + Private setter for rod_bottom_z attribute. """ if isinstance(value, int): value = float(value) if not isinstance(value, float): - raise TypeError("Expected 'float' type for 'plate_bottom_z' attribute.") - self._plate_bottom_z = value + raise TypeError("Expected 'float' type for 'rod_bottom_z' attribute.") + self._rod_bottom_z = value def _set_ground_surface_z(self, value: float) -> None: """ @@ -300,33 +305,33 @@ def date_time(self) -> datetime.datetime: return self._date_time @property - def coordinate_reference_system(self) -> pyproj.CRS: + def coordinate_reference_systems(self) -> CoordinateReferenceSystems: """ - The coordinate reference system of the spatial measurements. - It is a `pyproj.CRS` object (see https://pyproj4.github.io/pyproj/stable/api/crs/crs.html). + The horizontal (X, Y) and vertical (Z) coordinate reference systems (CRS) of the + spatial measurements. """ - return self._coordinate_reference_system + return self._coordinate_reference_systems @property - def x(self) -> float: + def rod_top_x(self) -> float: """ - The X-coordinate of the measurement point. + The horizontal X-coordinate of the top of the settlement rod. Units are according to the `coordinate_reference_system`. """ return self._x @property - def y(self) -> float: + def rod_top_y(self) -> float: """ - The Y-coordinate of the measurement point. + The horizontal Y-coordinate of the top of the settlement rod. Units are according to the `coordinate_reference_system`. """ return self._y @property - def z(self) -> float: + def rod_top_z(self) -> float: """ - The Z-coordinate of the measurement point. + The vertical Z-coordinate of the top of the settlement rod. It is the top of the settlement rod. Units are according to the `coordinate_reference_system`. """ @@ -336,28 +341,28 @@ def z(self) -> float: def rod_length(self) -> float: """ The length of the settlement rod including the thickness of the settlement plate. - It is in principle the vertical distance between the measurement point and the bottom of the settlement plate. + It is in principle the vertical distance between the top of the settlement rod and the bottom of the settlement plate. Units are according to the `coordinate_reference_system`. """ return self._rod_length @property - def plate_bottom_z(self) -> float: + def rod_bottom_z(self) -> float: """ - The corrected Z-coordinate at the bottom of the settlement plate. + The corrected Z-coordinate at the bottom of the settlement rod (coincides with bottom of settlement plate). Note that the bottom of the plate is in principle the original ground surface. Units are according to the `coordinate_reference_system`. """ - return self._plate_bottom_z + return self._rod_bottom_z @property - def plate_bottom_z_uncorrected(self) -> float: + def rod_bottom_z_uncorrected(self) -> float: """ - The uncorrected Z-coordinate at the bottom of the settlement plate. - It is computed as the difference beteen the Z-coordinate of the measurement point and the vertical offset. + The uncorrected Z-coordinate at the bottom of the settlement rod (coincides with bottom of settlement plate). + It is computed as the difference beteen the Z-coordinate of the top of the settlement rod and the rod length. Units are according to the `coordinate_reference_system`. """ - return self.z - self.rod_length + return self.rod_top_z - self.rod_length @property def ground_surface_z(self) -> float: @@ -394,3 +399,33 @@ def comment(self) -> str: Additional comment about the measurement. """ return self._comment + + @cache + def to_dict(self) -> dict: + """ + Convert the measurement to a dictionary. + """ + return { + "project_id": self.project.id, + "project_name": self.project.name, + "device_id": self.device.id, + "device_qr_code": self.device.qr_code, + "object_id": self.object_id, + "coordinate_horizontal_epsg_code": self.coordinate_reference_systems.horizontal.to_epsg(), + "coordinate_vertical_epsg_code": self.coordinate_reference_systems.vertical.to_epsg(), + "coordinate_horizontal_units": self.coordinate_reference_systems.horizontal_units, + "coordinate_vertical_units": self.coordinate_reference_systems.vertical_units, + "coordinate_vertical_datum": self.coordinate_reference_systems.vertical_datum, + "date_time": self.date_time, + "rod_top_x": self.rod_top_x, + "rod_top_y": self.rod_top_y, + "rod_top_z": self.rod_top_z, + "rod_length": self.rod_length, + "rod_bottom_z": self.rod_bottom_z, + "rod_bottom_z_uncorrected": self.rod_bottom_z_uncorrected, + "ground_surface_z": self.ground_surface_z, + "status": self.status.value, + "temperature": self.temperature, + "voltage": self.voltage, + "comment": self.comment, + } diff --git a/src/baec/measurements/settlement_rod_measurement_series.py b/src/baec/measurements/settlement_rod_measurement_series.py index 61c2514..4916256 100644 --- a/src/baec/measurements/settlement_rod_measurement_series.py +++ b/src/baec/measurements/settlement_rod_measurement_series.py @@ -1,10 +1,17 @@ from __future__ import annotations +from functools import cache from typing import List import pandas as pd +from matplotlib import pyplot as plt +from matplotlib.axes import Axes +from matplotlib.figure import Figure +from baec.coordinates import CoordinateReferenceSystems +from baec.measurements.measurement_device import MeasurementDevice from baec.measurements.settlement_rod_measurement import SettlementRodMeasurement +from baec.project import Project class SettlementRodMeasurementSeries: @@ -27,7 +34,8 @@ def __init__(self, measurements: List[SettlementRodMeasurement]) -> None: If the input types are incorrect. ValueError If the list of measurements is empty. - If the measurements are not for the same project, device or object. + If the measurements are not for the same project, device, object or + coordinate refence systems. """ # Initialize all attributes using private setters. @@ -52,7 +60,10 @@ def _set_measurements(self, value: List[SettlementRodMeasurement]) -> None: if measurement.project not in projects: projects.append(measurement.project) if len(projects) > 1: - raise ValueError("All measurements must be for the same project.") + raise ValueError( + "All measurements must be for the same project. " + + f"The following projects are found: {projects}" + ) # Check that the measurements are for the same device. measurement_devices = [] @@ -60,7 +71,10 @@ def _set_measurements(self, value: List[SettlementRodMeasurement]) -> None: if measurement.device not in measurement_devices: measurement_devices.append(measurement.device) if len(measurement_devices) > 1: - raise ValueError("All measurements must be for the same device.") + raise ValueError( + "All measurements must be for the same device. " + + f"The following devices are found: {measurement_devices}" + ) # Check that the measurements are for the same object. object_ids = [] @@ -68,7 +82,21 @@ def _set_measurements(self, value: List[SettlementRodMeasurement]) -> None: if measurement.object_id not in object_ids: object_ids.append(measurement.object_id) if len(object_ids) > 1: - raise ValueError("All measurements must be for the same measured object.") + raise ValueError( + "All measurements must be for the same measured object. " + + f"The following object IDs are found: {object_ids}" + ) + + # Check that the measurements are all in the same coordinate reference systems. + crs_list = [] + for measurement in value: + if measurement.coordinate_reference_systems not in crs_list: + crs_list.append(measurement.coordinate_reference_systems) + if len(crs_list) > 1: + raise ValueError( + "All measurements must be in the same coordinate reference systems. " + + f"The following object IDs are found: {crs_list}" + ) # Organize the list of measurements in chronological order. self._measurements = sorted(value, key=lambda x: x.date_time) @@ -81,6 +109,35 @@ def measurements(self) -> List[SettlementRodMeasurement]: """ return self._measurements + @property + def project(self) -> Project: + """ + The project which all the measurements belongs to. + """ + return self._measurements[0].project + + @property + def device(self) -> MeasurementDevice: + """ + The measurement device. + """ + return self._measurements[0].device + + @property + def object_id(self) -> str: + """ + The ID of the measured object. + """ + return self._measurements[0].object_id + + @property + def coordinate_reference_systems(self) -> CoordinateReferenceSystems: + """ + The horizontal (X, Y) and vertical (Z) coordinate reference systems of the measurements. + """ + return self._measurements[0].coordinate_reference_systems + + @cache def to_dataframe(self) -> pd.DataFrame: """ Convert the series of measurements to a pandas DataFrame. @@ -90,48 +147,208 @@ def to_dataframe(self) -> pd.DataFrame: pd.DataFrame A pandas DataFrame with the measurements. The columns of the DataFrame are: project_id, project_name, device_id, device_qr_code, object_id, - coordinate_epsg_code, date_time, x, y, z, rod_length, plate_bottom_z - plate_bottom_z_uncorrected, ground_surface_z, status, temperature, + coordinate_horizontal_epsg_code, coordinate_vertical_epsg_code, + date_time, rod_top_x, rod_top_y, rod_top_z, rod_length, rod_bottom_z + rod_bottom_z_uncorrected, ground_surface_z, status, temperature, voltage, comment. """ - # Initialize an empty DataFrame. - df = pd.DataFrame() - - # Add columns to the DataFrame. - df["project_id"] = [measurement.project.id for measurement in self.measurements] - df["project_name"] = [ - measurement.project.name for measurement in self.measurements - ] - df["device_id"] = [measurement.device.id for measurement in self.measurements] - df["device_qr_code"] = [ - measurement.device.qr_code for measurement in self.measurements - ] - df["object_id"] = [measurement.object_id for measurement in self.measurements] - df["coordinate_epsg_code"] = [ - measurement.coordinate_reference_system.to_epsg() - for measurement in self.measurements - ] - df["date_time"] = pd.to_datetime( - [measurement.date_time for measurement in self.measurements] + return pd.DataFrame.from_records( + [measurement.to_dict() for measurement in self.measurements] + ) + + def plot_x_time(self, axes: Axes | None = None) -> Axes: + """ + Plot the horizontal X-coordinates at the top of the rod over time. + + Parameters + ---------- + axes: plt.Axes + Axes to create the figure + + Returns + ------- + plt.Axes + """ + if axes is not None: + if not isinstance(axes, Axes): + raise TypeError("Expected 'Axes' type or None for 'axes' parameter.") + + if axes is None: + axes = plt.gca() + + df = self.to_dataframe() + + df.plot( + x="date_time", + y="rod_top_x", + ax=axes, + legend=True, + ) + + axes.set_ylim(df["rod_top_x"].min() - 0.5, df["rod_top_x"].max() + 0.5) + axes.grid() + + axes.set_ylabel(f"X [{self.coordinate_reference_systems.horizontal_units}]") + axes.set_xlabel("Date and Time") + axes.set_title(f"Horizontal X measurements for object: {self.object_id}") + + return axes + + def plot_y_time(self, axes: Axes | None = None) -> Axes: + """ + Plot the horizontal Y-coordinates at the top of the rod over time. + + Parameters + ---------- + axes: plt.Axes + Axes to create the figure + + Returns + ------- + plt.Axes + """ + if axes is not None: + if not isinstance(axes, Axes): + raise TypeError("Expected 'Axes' type or None for 'axes' parameter.") + + if axes is None: + axes = plt.gca() + + df = self.to_dataframe() + + df.plot( + x="date_time", + y="rod_top_y", + ax=axes, + legend=True, + ) + + axes.set_ylim(df["rod_top_y"].min() - 0.5, df["rod_top_y"].max() + 0.5) + axes.grid() + + axes.set_ylabel(f"Y [{self.coordinate_reference_systems.horizontal_units}]") + axes.set_xlabel("Date and Time") + axes.set_title(f"Horizontal Y measurements for object: {self.object_id}") + + return axes + + def plot_z_time(self, axes: Axes | None = None) -> Axes: + """ + Plot the vertical Z-coordinates at the top the rod, the ground surface and the bottom of the + rod over time. + + Parameters + ---------- + axes: plt.Axes + Axes to create the figure + + Returns + ------- + plt.Axes + """ + if axes is not None: + if not isinstance(axes, Axes): + raise TypeError("Expected 'Axes' type or None for 'axes' parameter.") + + if axes is None: + axes = plt.gca() + + df = self.to_dataframe() + z_cols = ["rod_top_z", "ground_surface_z", "rod_bottom_z"] + + df.plot( + x="date_time", + y=z_cols, + ax=axes, + legend=True, ) - df["x"] = [measurement.x for measurement in self.measurements] - df["y"] = [measurement.y for measurement in self.measurements] - df["z"] = [measurement.z for measurement in self.measurements] - df["rod_length"] = [measurement.rod_length for measurement in self.measurements] - df["plate_bottom_z"] = [ - measurement.plate_bottom_z for measurement in self.measurements - ] - df["plate_bottom_z_uncorrected"] = [ - measurement.plate_bottom_z_uncorrected for measurement in self.measurements - ] - df["ground_surface_z"] = [ - measurement.ground_surface_z for measurement in self.measurements - ] - df["status"] = [measurement.status.value for measurement in self.measurements] - df["temperature"] = [ - measurement.temperature for measurement in self.measurements - ] - df["voltage"] = [measurement.voltage for measurement in self.measurements] - df["comment"] = [measurement.comment for measurement in self.measurements] - - return df + + axes.set_ylim(df[z_cols].values.min() - 0.5, df[z_cols].values.max() + 0.5) + axes.grid() + + axes.set_ylabel(self.coordinate_reference_systems.vertical_datum_and_units) + axes.set_xlabel("Date and Time") + axes.set_title(f"Vertical Z measurements for object: {self.object_id}") + + return axes + + def plot_xyz_time(self) -> Figure: + """ + Plot in a new figure the horizontal XY-coordinates at the top of rod and the + vertical Z-coordinates at the top the rod, the ground surface and the bottom of the + rod over time. + + Returns + ------- + plt.Figure + """ + fig, axes = plt.subplots(3, 1, figsize=(10, 30), sharex=True) + + fig.suptitle(f"Spatial measurements for object: {self.object_id}") + + self.plot_x_time(axes[0]) + axes[0].set_title("Horizontal X") + + self.plot_y_time(axes[1]) + axes[1].set_title("Horizontal Y") + + self.plot_z_time(axes[2]) + axes[2].set_title("Vertical Z") + + return fig + + def plot_xy_plan_view(self, axes: Axes | None = None) -> Axes: + """ + Plot the plan view of the horizontal XY coordinates + at the top of the rod. + + Parameters + ---------- + axes: plt.Axes + Axes to create the figure + + Returns + ------- + plt.Axes + """ + if axes is not None: + if not isinstance(axes, Axes): + raise TypeError("Expected 'Axes' type or None for 'axes' parameter.") + + if axes is None: + axes = plt.gca() + + df = self.to_dataframe() + + axes.plot(df["rod_top_x"], df["rod_top_y"]) + + # Mark the start and end of the measurements. + axes.plot( + df["rod_top_x"].iloc[0], + df["rod_top_y"].iloc[0], + marker="*", + color="black", + label="start", + ) + + axes.plot( + df["rod_top_x"].iloc[-1], + df["rod_top_y"].iloc[-1], + marker="+", + color="red", + label="end", + ) + + axes.legend(loc="upper right") + + axes.set_xlim(df["rod_top_x"].min() - 0.5, df["rod_top_x"].max() + 0.5) + axes.set_ylim(df["rod_top_y"].min() - 0.5, df["rod_top_y"].max() + 0.5) + axes.grid() + + axes.set_xlabel(f"X [{self.coordinate_reference_systems.horizontal_units}]") + axes.set_ylabel(f"Y [{self.coordinate_reference_systems.horizontal_units}]") + axes.set_title( + f"Plan view of horizonal measurements at rod top for object: {self.object_id}" + ) + + return axes diff --git a/tests/measurements/conftest.py b/tests/measurements/conftest.py index ddc71dc..c599c24 100644 --- a/tests/measurements/conftest.py +++ b/tests/measurements/conftest.py @@ -1,9 +1,9 @@ import datetime from typing import List -import pyproj import pytest +from baec.coordinates import CoordinateReferenceSystems from baec.measurements.measurement_device import MeasurementDevice from baec.measurements.settlement_rod_measurement import ( SettlementRodMeasurement, @@ -18,10 +18,10 @@ def example_settlement_rod_measurements() -> List[SettlementRodMeasurement]: device = MeasurementDevice(id_="BR_003", qr_code="QR-003") object_id = "ZB-02" date_time_start = datetime.datetime(2024, 4, 9, 4, 0, 0) - coordinate_reference_system = pyproj.CRS.from_user_input(28992) - x = 123340.266 - y = 487597.154 - z_start = 0.807 + coordinate_reference_systems = CoordinateReferenceSystems.from_epsg(28992, 5709) + rod_top_x_start = 123340.266 + rod_top_y_start = 487597.154 + rod_top_z_start = 0.807 rod_length = 2.0 plate_bottom_z = -1.193 ground_surface_z = 0.419 @@ -32,7 +32,9 @@ def example_settlement_rod_measurements() -> List[SettlementRodMeasurement]: measurements = [] for i in range(10): - z = z_start - 0.01 * i + rod_top_x = rod_top_x_start + 0.05 * i + rod_top_y = rod_top_y_start - 0.03 * i + rod_top_z = rod_top_z_start - 0.01 * i date_time = date_time_start + datetime.timedelta(days=i) measurements.append( SettlementRodMeasurement( @@ -40,12 +42,12 @@ def example_settlement_rod_measurements() -> List[SettlementRodMeasurement]: device=device, object_id=object_id, date_time=date_time, - coordinate_reference_system=coordinate_reference_system, - x=x, - y=y, - z=z, + coordinate_reference_systems=coordinate_reference_systems, + rod_top_x=rod_top_x, + rod_top_y=rod_top_y, + rod_top_z=rod_top_z, rod_length=rod_length, - plate_bottom_z=plate_bottom_z, + rod_bottom_z=plate_bottom_z, ground_surface_z=ground_surface_z, status=status, temperature=temperature, diff --git a/tests/measurements/test_settlement_rod_measurement.py b/tests/measurements/test_settlement_rod_measurement.py index 455caa2..9ce951c 100644 --- a/tests/measurements/test_settlement_rod_measurement.py +++ b/tests/measurements/test_settlement_rod_measurement.py @@ -3,6 +3,7 @@ import pyproj import pytest +from baec.coordinates import CoordinateReferenceSystems from baec.measurements.measurement_device import MeasurementDevice from baec.measurements.settlement_rod_measurement import ( SettlementRodMeasurement, @@ -17,12 +18,12 @@ def test_settlement_rod_measurement_init_with_valid_input() -> None: device = MeasurementDevice(id_="BR_003", qr_code="QR-003") object_id = "ZB-02" date_time = datetime.datetime(2024, 4, 9, 4, 0, 0) - coordinate_reference_system = pyproj.CRS.from_user_input(28992) - x = 123340.266 - y = 487597.154 - z = 0.807 + coordinate_reference_systems = CoordinateReferenceSystems.from_epsg(28992, 5709) + rod_top_x = 123340.266 + rod_top_y = 487597.154 + rod_top_z = 0.807 rod_length = 2.0 - plate_bottom_z = -1.193 + rod_bottom_z = -1.193 ground_surface_z = 0.419 status = SettlementRodMeasurementStatus.OK temperature = 12.0 @@ -35,12 +36,12 @@ def test_settlement_rod_measurement_init_with_valid_input() -> None: device=device, object_id=object_id, date_time=date_time, - coordinate_reference_system=coordinate_reference_system, - x=x, - y=y, - z=z, + coordinate_reference_systems=coordinate_reference_systems, + rod_top_x=rod_top_x, + rod_top_y=rod_top_y, + rod_top_z=rod_top_z, rod_length=rod_length, - plate_bottom_z=plate_bottom_z, + rod_bottom_z=rod_bottom_z, ground_surface_z=ground_surface_z, status=status, temperature=temperature, @@ -52,15 +53,15 @@ def test_settlement_rod_measurement_init_with_valid_input() -> None: assert measurement.device == device assert measurement.object_id == object_id assert measurement.date_time == date_time - assert measurement.coordinate_reference_system == coordinate_reference_system - assert measurement.x == x - assert measurement.y == y - assert measurement.z == z + assert measurement.coordinate_reference_systems == coordinate_reference_systems + assert measurement.rod_top_x == rod_top_x + assert measurement.rod_top_y == rod_top_y + assert measurement.rod_top_z == rod_top_z assert measurement.rod_length == rod_length assert measurement.ground_surface_z == ground_surface_z - assert measurement.plate_bottom_z == plate_bottom_z + assert measurement.rod_bottom_z == rod_bottom_z assert measurement.status == status - assert measurement.plate_bottom_z_uncorrected == plate_bottom_z_uncorrected + assert measurement.rod_bottom_z_uncorrected == plate_bottom_z_uncorrected assert measurement.temperature == temperature assert measurement.voltage == voltage assert measurement.comment == comment @@ -75,12 +76,14 @@ def test_settlement_rod_measurement_init_with_invalid_project() -> None: device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -98,12 +101,14 @@ def test_settlement_rod_measurement_init_with_invalid_device() -> None: device=None, object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -121,12 +126,14 @@ def test_settlement_rod_measurement_init_with_invalid_object_id() -> None: device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id=None, date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -141,12 +148,14 @@ def test_settlement_rod_measurement_init_with_invalid_object_id() -> None: device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -164,12 +173,14 @@ def test_settlement_rod_measurement_init_with_invalid_date_time() -> None: device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=None, - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -178,23 +189,23 @@ def test_settlement_rod_measurement_init_with_invalid_date_time() -> None: ) -def test_settlement_rod_measurement_init_with_invalid_coordinate_reference_system() -> ( +def test_settlement_rod_measurement_init_with_invalid_coordinate_reference_systems() -> ( None ): - """Test initialization of settlement rod measurement with invalid coordinate reference system.""" + """Test initialization of settlement rod measurement with invalid coordinate reference systems.""" # Invalid coordinate_reference_system: None - with pytest.raises(TypeError, match="coordinate_reference_system"): + with pytest.raises(TypeError, match="coordinate_reference_systems"): SettlementRodMeasurement( project=Project(id_="P-001", name="Project 1"), device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=None, - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=None, + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -203,21 +214,23 @@ def test_settlement_rod_measurement_init_with_invalid_coordinate_reference_syste ) -def test_settlement_rod_measurement_init_with_invalid_x() -> None: - """Test initialization of settlement rod measurement with invalid x.""" - # Invalid x: String value - with pytest.raises(TypeError, match="x"): +def test_settlement_rod_measurement_init_with_invalid_rod_top_x() -> None: + """Test initialization of settlement rod measurement with invalid rod_top_x.""" + # Invalid rod_top_x: String value + with pytest.raises(TypeError, match="rod_top_x"): SettlementRodMeasurement( project=Project(id_="P-001", name="Project 1"), device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x="123340.266", - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x="123340.266", + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -226,21 +239,23 @@ def test_settlement_rod_measurement_init_with_invalid_x() -> None: ) -def test_settlement_rod_measurement_init_with_invalid_y() -> None: - """Test initialization of settlement rod measurement with invalid y.""" - # Invalid y: None - with pytest.raises(TypeError, match="y"): +def test_settlement_rod_measurement_init_with_invalid_rod_top_y() -> None: + """Test initialization of settlement rod measurement with invalid rod_top_y.""" + # Invalid rod_top_y: None + with pytest.raises(TypeError, match="rod_top_y"): SettlementRodMeasurement( project=Project(id_="P-001", name="Project 1"), device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=None, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=None, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -249,21 +264,23 @@ def test_settlement_rod_measurement_init_with_invalid_y() -> None: ) -def test_settlement_rod_measurement_init_with_z() -> None: - """Test initialization of settlement rod measurement with invalid z.""" - # Invalid z: String value - with pytest.raises(TypeError, match="z"): +def test_settlement_rod_measurement_init_with_rod_top_z() -> None: + """Test initialization of settlement rod measurement with invalid rod_top_z.""" + # Invalid rod_top_z: String value + with pytest.raises(TypeError, match="rod_top_z"): SettlementRodMeasurement( project=Project(id_="P-001", name="Project 1"), device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z="0.807", + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z="0.807", rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -281,12 +298,14 @@ def test_settlement_rod_measurement_init_with_invalid_rod_length() -> None: device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length="2.0", - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -301,12 +320,14 @@ def test_settlement_rod_measurement_init_with_invalid_rod_length() -> None: device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=-2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -324,12 +345,14 @@ def test_settlement_rod_measurement_init_with_invalid_ground_surface_z() -> None device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z="0.419", status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -338,21 +361,23 @@ def test_settlement_rod_measurement_init_with_invalid_ground_surface_z() -> None ) -def test_settlement_rod_measurement_init_with_invalid_plate_bottom_z() -> None: - """Test initialization of settlement rod measurement with invalid plate_bottom_z.""" - # Invalid plate_bottom_z: String value - with pytest.raises(TypeError, match="plate_bottom_z"): +def test_settlement_rod_measurement_init_with_invalid_rod_bottom_z() -> None: + """Test initialization of settlement rod measurement with invalid rod_bottom_z.""" + # Invalid rod_bottom_z: String value + with pytest.raises(TypeError, match="rod_bottom_z"): SettlementRodMeasurement( project=Project(id_="P-001", name="Project 1"), device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z="-1.193", + rod_bottom_z="-1.193", ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -370,12 +395,14 @@ def test_settlement_rod_measurement_init_with_invalid_temperature() -> None: device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature="12.0", @@ -393,12 +420,14 @@ def test_settlement_rod_measurement_init_with_invalid_voltage() -> None: device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, @@ -416,12 +445,14 @@ def test_settlement_rod_measurement_init_with_invalid_comment() -> None: device=MeasurementDevice(id_="BR_003", qr_code="QR-003"), object_id="ZB-02", date_time=datetime.datetime(2024, 4, 9, 4, 0, 0), - coordinate_reference_system=pyproj.CRS.from_user_input(28992), - x=123340.266, - y=487597.154, - z=0.807, + coordinate_reference_systems=CoordinateReferenceSystems.from_epsg( + 28992, 5709 + ), + rod_top_x=123340.266, + rod_top_y=487597.154, + rod_top_z=0.807, rod_length=2.0, - plate_bottom_z=-1.193, + rod_bottom_z=-1.193, ground_surface_z=0.419, status=SettlementRodMeasurementStatus.OK, temperature=12.0, diff --git a/tests/measurements/test_settlement_rod_measurement_series.py b/tests/measurements/test_settlement_rod_measurement_series.py index 89722f6..18c6ab9 100644 --- a/tests/measurements/test_settlement_rod_measurement_series.py +++ b/tests/measurements/test_settlement_rod_measurement_series.py @@ -1,10 +1,11 @@ -import datetime from copy import deepcopy from typing import List -import pyproj import pytest +from matplotlib import pyplot as plt +from pandas import show_versions +from baec.coordinates import CoordinateReferenceSystems from baec.measurements.measurement_device import MeasurementDevice from baec.measurements.settlement_rod_measurement import SettlementRodMeasurement from baec.measurements.settlement_rod_measurement_series import ( @@ -68,13 +69,31 @@ def test_settlement_rod_measurement_series_init_with_invalid_measurements( with pytest.raises(ValueError, match="device"): SettlementRodMeasurementSeries(measurements=measurements) - # Different objects + # Different measured objects measurements = deepcopy(example_settlement_rod_measurements) measurements[0]._object_id = "ZB-20" with pytest.raises(ValueError, match="object"): SettlementRodMeasurementSeries(measurements=measurements) + # Different coordinate reference systems (horizontal) + measurements = deepcopy(example_settlement_rod_measurements) + measurements[ + 0 + ]._coordinate_reference_systems = CoordinateReferenceSystems.from_epsg(28992, 5710) + + with pytest.raises(ValueError, match="coordinate reference systems"): + SettlementRodMeasurementSeries(measurements=measurements) + + # Different coordinate reference systems (vertical) + measurements = deepcopy(example_settlement_rod_measurements) + measurements[ + 0 + ]._coordinate_reference_systems = CoordinateReferenceSystems.from_epsg(31370, 5709) + + with pytest.raises(ValueError, match="coordinate reference systems"): + SettlementRodMeasurementSeries(measurements=measurements) + def test_settlement_rod_measurement_series_to_dataframe_method( example_settlement_rod_measurements: List[SettlementRodMeasurement], @@ -95,21 +114,153 @@ def test_settlement_rod_measurement_series_to_dataframe_method( assert df.iloc[i]["device_id"] == measurement.device.id assert df.iloc[i]["object_id"] == measurement.object_id assert ( - df.iloc[i]["coordinate_epsg_code"] - == measurement.coordinate_reference_system.to_epsg() + df.iloc[i]["coordinate_horizontal_epsg_code"] + == measurement.coordinate_reference_systems.horizontal.to_epsg() + ) + assert ( + df.iloc[i]["coordinate_vertical_epsg_code"] + == measurement.coordinate_reference_systems.vertical.to_epsg() + ) + assert df.iloc[i]["coordinate_horizontal_units"] == ( + measurement.coordinate_reference_systems.horizontal_units + ) + assert df.iloc[i]["coordinate_vertical_units"] == ( + measurement.coordinate_reference_systems.vertical_units + ) + assert df.iloc[i]["coordinate_vertical_datum"] == ( + measurement.coordinate_reference_systems.vertical_datum ) assert df.iloc[i]["date_time"] == measurement.date_time - assert df.iloc[i]["x"] == measurement.x - assert df.iloc[i]["y"] == measurement.y - assert df.iloc[i]["z"] == measurement.z + assert df.iloc[i]["rod_top_x"] == measurement.rod_top_x + assert df.iloc[i]["rod_top_y"] == measurement.rod_top_y + assert df.iloc[i]["rod_top_z"] == measurement.rod_top_z assert df.iloc[i]["rod_length"] == measurement.rod_length - assert df.iloc[i]["plate_bottom_z"] == measurement.plate_bottom_z + assert df.iloc[i]["rod_bottom_z"] == measurement.rod_bottom_z assert ( - df.iloc[i]["plate_bottom_z_uncorrected"] - == measurement.plate_bottom_z_uncorrected + df.iloc[i]["rod_bottom_z_uncorrected"] + == measurement.rod_bottom_z_uncorrected ) assert df.iloc[i]["ground_surface_z"] == measurement.ground_surface_z assert df.iloc[i]["status"] == measurement.status.value assert df.iloc[i]["temperature"] == measurement.temperature assert df.iloc[i]["voltage"] == measurement.voltage assert df.iloc[i]["comment"] == measurement.comment + + +def test_plot_x_time( + example_settlement_rod_measurements: List[SettlementRodMeasurement], +) -> None: + """Test plot_x_time method are generated without error.""" + + show = False + + series = SettlementRodMeasurementSeries( + measurements=example_settlement_rod_measurements + ) + + # Plot without giving axes + ax = series.plot_x_time() + if show: + plt.show() + + # Plot giving axes + _, ax = plt.subplots() + series.plot_x_time(ax) + if show: + plt.show() + + plt.close("all") + + +def test_plot_y_time( + example_settlement_rod_measurements: List[SettlementRodMeasurement], +) -> None: + """Test plot_y_time method are generated without error.""" + + show = False + + series = SettlementRodMeasurementSeries( + measurements=example_settlement_rod_measurements + ) + + # Plot without giving axes + ax = series.plot_y_time() + if show: + plt.show() + + # Plot giving axes + _, ax = plt.subplots() + series.plot_y_time(ax) + if show: + plt.show() + + plt.close("all") + + +def test_plot_z_time( + example_settlement_rod_measurements: List[SettlementRodMeasurement], +) -> None: + """Test plot_z_time method are generated without error.""" + + show = False + + series = SettlementRodMeasurementSeries( + measurements=example_settlement_rod_measurements + ) + + # Plot without giving axes + ax = series.plot_z_time() + if show: + plt.show() + + # Plot giving axes + _, ax = plt.subplots() + series.plot_z_time(ax) + if show: + plt.show() + + plt.close("all") + + +def test_plot_xyz_time( + example_settlement_rod_measurements: List[SettlementRodMeasurement], +) -> None: + """Test plot_xyz_time method are generated without error.""" + + show = False + + series = SettlementRodMeasurementSeries( + measurements=example_settlement_rod_measurements + ) + + # Plot without giving axes + series.plot_xyz_time() + if show: + plt.show() + + plt.close() + + +def test_plot_xy_plan_view( + example_settlement_rod_measurements: List[SettlementRodMeasurement], +) -> None: + """Test plot_xy_plan_view method are generated without error.""" + + show = False + + series = SettlementRodMeasurementSeries( + measurements=example_settlement_rod_measurements + ) + + # Plot without giving axes + ax = series.plot_xy_plan_view() + if show: + plt.show() + + # Plot giving axes + _, ax = plt.subplots() + series.plot_xy_plan_view(ax) + if show: + plt.show() + + plt.close("all") diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py new file mode 100644 index 0000000..854d06f --- /dev/null +++ b/tests/test_coordinates.py @@ -0,0 +1,88 @@ +import pyproj +import pytest + +from baec.coordinates import CoordinateReferenceSystems + + +def test_coordinate_reference_system_init_with_valid_input() -> None: + """Test initialization of CoordinateReferenceSystems with valid input.""" + + # With projected and vertical CRS + horizontal_crs = pyproj.CRS.from_epsg(28992) + vertical_crs = pyproj.CRS.from_epsg(5710) + crs = CoordinateReferenceSystems(horizontal=horizontal_crs, vertical=vertical_crs) + assert crs.horizontal == horizontal_crs + assert crs.vertical == vertical_crs + + # With compound CRS + horizontal_crs = pyproj.CRS.from_epsg(7415) + vertical_crs = pyproj.CRS.from_epsg(7415) + crs = CoordinateReferenceSystems(horizontal=horizontal_crs, vertical=vertical_crs) + assert crs.horizontal == horizontal_crs + assert crs.vertical == vertical_crs + + +def test_coordinate_reference_system_init_with_invalid_horizontal_CRS() -> None: + """Test initialization of CoordinateReferenceSystems with invalid horizontal CRS.""" + # Invalid horizontal CRS: None + with pytest.raises(TypeError, match="horizontal"): + CoordinateReferenceSystems(horizontal=None, vertical=pyproj.CRS.from_epsg(5710)) + + # Invalid horizontal CRS: Vertical CRS + with pytest.raises(ValueError, match="horizontal"): + CoordinateReferenceSystems( + horizontal=pyproj.CRS.from_epsg(5710), vertical=pyproj.CRS.from_epsg(5710) + ) + + +def test_coordinate_reference_system_init_with_invalid_vertical_CRS() -> None: + """Test initialization of CoordinateReferenceSystems with invalid vertical CRS.""" + # Invalid vertical CRS: None + with pytest.raises(TypeError, match="vertical"): + CoordinateReferenceSystems( + horizontal=pyproj.CRS.from_epsg(28992), vertical=None + ) + + # Invalid vertical: Projected CRS + with pytest.raises(ValueError, match="vertical"): + CoordinateReferenceSystems( + horizontal=pyproj.CRS.from_epsg(28992), vertical=pyproj.CRS.from_epsg(28992) + ) + + +def test_coordinate_reference_system_from_epsg() -> None: + """Test constructor method `from_espg`.""" + # Valid input + crs = CoordinateReferenceSystems.from_epsg(horizontal=28992, vertical=5710) + assert crs.horizontal == pyproj.CRS.from_epsg(28992) + assert crs.vertical == pyproj.CRS.from_epsg(5710) + + # Invalid horizontal EPSG code + with pytest.raises(pyproj.exceptions.CRSError): + CoordinateReferenceSystems.from_epsg(horizontal=99999, vertical=5710) + + # Invalid vertical EPSG code + with pytest.raises(pyproj.exceptions.CRSError): + CoordinateReferenceSystems.from_epsg(horizontal=28992, vertical=99999) + + +def test_coordinate_reference_system__eq__method() -> None: + """Test the __eq__ method of CoordinateReferenceSystems.""" + crs_1 = CoordinateReferenceSystems.from_epsg(28992, 5709) + crs_2 = CoordinateReferenceSystems.from_epsg(28992, 5709) + crs_3 = CoordinateReferenceSystems.from_epsg(31370, 5710) + crs_4 = CoordinateReferenceSystems.from_epsg(28992, 5710) + crs_5 = CoordinateReferenceSystems.from_epsg(31370, 5709) + + assert crs_1 == crs_2 + assert crs_1 != crs_3 + assert crs_1 != crs_4 + assert crs_1 != crs_5 + assert crs_2 != crs_3 + assert crs_2 != crs_4 + assert crs_3 != crs_4 + assert crs_4 != crs_5 + + assert crs_1 == crs_1 + assert crs_1 != None + assert crs_1 != "EPSG:4326" From 88d897dee41bd0757613fe4f4e0e5e2f03a49803 Mon Sep 17 00:00:00 2001 From: PabloVasconez Date: Mon, 10 Jun 2024 13:41:46 +0200 Subject: [PATCH 6/7] Update src/baec/coordinates.py Co-authored-by: Robin Wimmers <46839685+RDWimmers@users.noreply.github.com> --- src/baec/coordinates.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/baec/coordinates.py b/src/baec/coordinates.py index c850b28..949db3d 100644 --- a/src/baec/coordinates.py +++ b/src/baec/coordinates.py @@ -42,6 +42,9 @@ def __init__(self, horizontal: pyproj.CRS, vertical: pyproj.CRS) -> None: def from_epsg(cls, horizontal: int, vertical: int) -> CoordinateReferenceSystems: """ Creates a CoordinateReferenceSystems object from the EPSG codes of the horizontal and vertical CRS. +Notes: +----------- +If your settlement rod is located in the Netherlands the horizontal coordinate reference systems is likely `28992` (Amersfoort / RD New) and the vertical `5709` (NAP height). To combine use `7415` (Amersfoort / RD New + NAP height). Parameters: ---------- From d44341134e208cb9244bf7f5b2846e698f7a2635 Mon Sep 17 00:00:00 2001 From: Pablo Vasconez Date: Mon, 10 Jun 2024 13:54:44 +0200 Subject: [PATCH 7/7] apply(#18): add note to CoordinateReferenceSystems about NL and modify figure according to PR #18 comments --- docs/tree/figures/settlement_rod.png | Bin 36268 -> 37384 bytes docs/tree/reference.rst | 13 ++++++++++++- src/baec/coordinates.py | 14 ++++++++------ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/tree/figures/settlement_rod.png b/docs/tree/figures/settlement_rod.png index df2e79e5762ec5f5423ab033c0f64f0fddeeef64..b5bea10babd4bd8bbb8666518b39acdaee6f325d 100644 GIT binary patch literal 37384 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
Hv&ix6AvGY5Uajc&Q60ou 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 None: """ Initializes a CoordinateReferenceSystems object. - Parameters: + Parameters ---------- horizontal : pyproj.CRS The coordinate reference system of the X and Y-coordinates. @@ -42,18 +42,20 @@ def __init__(self, horizontal: pyproj.CRS, vertical: pyproj.CRS) -> None: def from_epsg(cls, horizontal: int, vertical: int) -> CoordinateReferenceSystems: """ Creates a CoordinateReferenceSystems object from the EPSG codes of the horizontal and vertical CRS. -Notes: ------------ -If your settlement rod is located in the Netherlands the horizontal coordinate reference systems is likely `28992` (Amersfoort / RD New) and the vertical `5709` (NAP height). To combine use `7415` (Amersfoort / RD New + NAP height). - Parameters: + Note + ---- + If your settlement rod is located in the Netherlands the horizontal coordinate reference systems is likely `28992` + (Amersfoort / RD New) and the vertical `5709` (NAP height). To combine use `7415` (Amersfoort / RD New + NAP height). + + Parameters ---------- horizontal : int The EPSG code of the horizontal CRS. vertical : int The EPSG code of the vertical CRS. - Returns: + Returns ------- CoordinateReferenceSystems A CoordinateReferenceSystems object with the horizontal and vertical CRS.