pysurveytools 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Stefan Printz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: pysurveytools
3
+ Version: 0.1.0
4
+ Summary: Geodetic classes for managing and calculating (Angle, Coordinate, Measurement, …)
5
+ Home-page: https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools
6
+ Author: Stefan Printz
7
+ Author-email: Stefan Printz <stefan.printz@hs-bochum.de>
8
+ License: MIT
9
+ Project-URL: Homepage, https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools
10
+ Project-URL: Issue Tracker, https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools/-/issues
11
+ Project-URL: Source, https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools.git
12
+ Keywords: geodesy,surveying,coordinates,angles,measurement
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy>=1.20
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=7.0; extra == "test"
21
+ Dynamic: author
22
+ Dynamic: home-page
23
+ Dynamic: license-file
24
+
25
+ # pySurveytools
26
+
27
+ **Geodetic classes for managing and calculating**
28
+
29
+ ## Overview
30
+
31
+ `surveytools` is a Python library providing essential geodetic classes and utilities to support surveying calculations and coordinate management. It offers robust implementations for angles, coordinates, measurements, and other related geospatial operations, enabling precise and efficient geodetic data processing.
32
+
33
+ ## Features
34
+
35
+ - Angle representation and conversion
36
+ - Coordinate handling in multiple formats
37
+ - Measurement encapsulation with metadata
38
+ - Easy integration with surveying instruments and software
39
+
40
+ ## Installation
41
+
42
+ You can install `surveytools` via pip:
43
+
44
+ ```bash
45
+ pip install pysurveytools
@@ -0,0 +1,21 @@
1
+ # pySurveytools
2
+
3
+ **Geodetic classes for managing and calculating**
4
+
5
+ ## Overview
6
+
7
+ `surveytools` is a Python library providing essential geodetic classes and utilities to support surveying calculations and coordinate management. It offers robust implementations for angles, coordinates, measurements, and other related geospatial operations, enabling precise and efficient geodetic data processing.
8
+
9
+ ## Features
10
+
11
+ - Angle representation and conversion
12
+ - Coordinate handling in multiple formats
13
+ - Measurement encapsulation with metadata
14
+ - Easy integration with surveying instruments and software
15
+
16
+ ## Installation
17
+
18
+ You can install `surveytools` via pip:
19
+
20
+ ```bash
21
+ pip install pysurveytools
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pysurveytools"
7
+ version = "0.1.0"
8
+ description = "Geodetic classes for managing and calculating (Angle, Coordinate, Measurement, …)"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Stefan Printz", email = "stefan.printz@hs-bochum.de" }
12
+ ]
13
+ license = { text = "MIT" }
14
+ urls = {"Homepage" = "https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools", "Issue Tracker" = "https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools/-/issues", "Source" = "https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools.git"}
15
+ keywords = ["geodesy", "surveying", "coordinates", "angles", "measurement"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent"
20
+ ]
21
+ dependencies = [
22
+ "numpy >= 1.20"
23
+ ]
24
+
25
+ [project.optional-dependencies]
26
+ test = [
27
+ "pytest >= 7.0"
28
+ ]
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["src"]
32
+
33
+ [project.scripts]
34
+ surveytools-cli = "surveytools.surveytools:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env python
2
+ import setuptools
3
+
4
+ if __name__ == "__main__":
5
+ setuptools.setup(
6
+ name="pysurveytools",
7
+ version="0.1.0",
8
+ author="Stefan Printz",
9
+ author_email="stefan.printz@hs-bochum.de",
10
+ description="Geodetic classes for managing and calculating (Angle, Coordinate, Measurement, …)",
11
+ long_description=open("README.md", encoding="utf-8").read(),
12
+ long_description_content_type="text/markdown",
13
+ url="https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools",
14
+ packages=setuptools.find_packages(where="src"),
15
+ package_dir={"": "src"},
16
+ install_requires=[
17
+ "numpy>=1.20",
18
+ ],
19
+ extras_require={
20
+ "test": ["pytest>=7.0"],
21
+ },
22
+ classifiers=[
23
+ "Programming Language :: Python :: 3",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Operating System :: OS Independent",
26
+ ],
27
+ entry_points={
28
+ "console_scripts": [
29
+ "surveytools-cli=surveytools.surveytools:main",
30
+ ],
31
+ },
32
+ )
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: pysurveytools
3
+ Version: 0.1.0
4
+ Summary: Geodetic classes for managing and calculating (Angle, Coordinate, Measurement, …)
5
+ Home-page: https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools
6
+ Author: Stefan Printz
7
+ Author-email: Stefan Printz <stefan.printz@hs-bochum.de>
8
+ License: MIT
9
+ Project-URL: Homepage, https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools
10
+ Project-URL: Issue Tracker, https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools/-/issues
11
+ Project-URL: Source, https://gitlab-ce.hs-bochum.de/fachbereich-geodaesie/pypi/pysurveytools.git
12
+ Keywords: geodesy,surveying,coordinates,angles,measurement
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy>=1.20
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=7.0; extra == "test"
21
+ Dynamic: author
22
+ Dynamic: home-page
23
+ Dynamic: license-file
24
+
25
+ # pySurveytools
26
+
27
+ **Geodetic classes for managing and calculating**
28
+
29
+ ## Overview
30
+
31
+ `surveytools` is a Python library providing essential geodetic classes and utilities to support surveying calculations and coordinate management. It offers robust implementations for angles, coordinates, measurements, and other related geospatial operations, enabling precise and efficient geodetic data processing.
32
+
33
+ ## Features
34
+
35
+ - Angle representation and conversion
36
+ - Coordinate handling in multiple formats
37
+ - Measurement encapsulation with metadata
38
+ - Easy integration with surveying instruments and software
39
+
40
+ ## Installation
41
+
42
+ You can install `surveytools` via pip:
43
+
44
+ ```bash
45
+ pip install pysurveytools
@@ -0,0 +1,22 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ src/pysurveytools.egg-info/PKG-INFO
6
+ src/pysurveytools.egg-info/SOURCES.txt
7
+ src/pysurveytools.egg-info/dependency_links.txt
8
+ src/pysurveytools.egg-info/entry_points.txt
9
+ src/pysurveytools.egg-info/requires.txt
10
+ src/pysurveytools.egg-info/top_level.txt
11
+ src/surveytools/__init__.py
12
+ src/surveytools/angle.py
13
+ src/surveytools/coordinate.py
14
+ src/surveytools/full_angle_measurement.py
15
+ src/surveytools/instrument_station.py
16
+ src/surveytools/measurement.py
17
+ src/surveytools/measurement_target.py
18
+ src/surveytools/surveytools.py
19
+ tests/test_angle.py
20
+ tests/test_coordinate.py
21
+ tests/test_measurement.py
22
+ tests/test_measurement_target.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ surveytools-cli = surveytools.surveytools:main
@@ -0,0 +1,4 @@
1
+ numpy>=1.20
2
+
3
+ [test]
4
+ pytest>=7.0
@@ -0,0 +1 @@
1
+ surveytools
@@ -0,0 +1,13 @@
1
+ # src/surveytools/__init__.py
2
+ from .surveytools import SWVersion
3
+ from .coordinate import Coordinate
4
+ from .angle import Angle, RHO
5
+ from .measurement import Measurement
6
+ from .measurement_target import MeasurementTarget
7
+ from .full_angle_measurement import FullAngleMeasurement
8
+
9
+ __all__ = [
10
+ "Angle", "RHO",
11
+ "Coordinate", "Measurement",
12
+ "SWVersion", "FullAngleMeasurement", "MeasurementTarget"
13
+ ]
@@ -0,0 +1,92 @@
1
+ import math
2
+ from functools import total_ordering
3
+
4
+ RHO = 200 / math.pi
5
+
6
+ @total_ordering
7
+ class Angle(object):
8
+
9
+ def __init__(self, rad: int = 0):
10
+ self.value_rad = rad
11
+
12
+ @staticmethod
13
+ def from_gon(gon_value):
14
+ r = Angle()
15
+ r.set_gon(gon_value)
16
+ return r
17
+
18
+ @staticmethod
19
+ def from_rad(rad_value):
20
+ r = Angle(rad_value)
21
+ return r
22
+
23
+ def set_gon(self, gon):
24
+ self.value_rad = gon / RHO
25
+ return
26
+
27
+ def get_gon(self):
28
+ return self.value_rad * RHO
29
+
30
+
31
+ def __abs__(self):
32
+ r = Angle()
33
+ r.value_rad = abs(self.value_rad)
34
+ return r
35
+
36
+ def __add__(self, o):
37
+ if isinstance(o, Angle):
38
+ r = Angle()
39
+ r.value_rad = self.value_rad + o.value_rad
40
+ return r
41
+ else:
42
+ raise TypeError("unsupported operand type(s) for +: 'Angle' and '" + str(type(o).__name__) + "'")
43
+
44
+ def __sub__(self, o):
45
+ if isinstance(o, Angle):
46
+ r = Angle()
47
+ r.value_rad = self.value_rad - o.value_rad
48
+ return r
49
+ else:
50
+ raise TypeError("unsupported operand type(s) for -: 'Angle' and '" + type(o).__name__ + "'")
51
+
52
+ def __truediv__(self, o):
53
+ if isinstance(o, (int, float)):
54
+ r = Angle()
55
+ r.value_rad = self.value_rad / o
56
+ else:
57
+ raise TypeError("unsupported operand type(s) for /: 'Angle' and '" + type(o).__name__ + "'")
58
+ return r
59
+
60
+ def __eq__(self, other):
61
+ if not isinstance(other, Angle):
62
+ return NotImplemented
63
+ return self.value_rad == other.value_rad
64
+
65
+ def __lt__(self, other):
66
+ if not isinstance(other, Angle):
67
+ return NotImplemented
68
+ return self.value_rad < other.value_rad
69
+
70
+ def __str__(self):
71
+ return '{0:.7} gon'.format(self.get_gon())
72
+
73
+ def normalise(self):
74
+ self.value_rad %= 2 * math.pi
75
+ return self
76
+
77
+ def add_half_circle(self):
78
+ r = Angle()
79
+ r.value_rad = self.value_rad + math.pi
80
+ r.normalise()
81
+ return r
82
+
83
+ def supplementary_angle(self):
84
+ r = Angle()
85
+ r.value_rad = 2 * math.pi - self.value_rad
86
+ return r
87
+
88
+ def sin(self):
89
+ return math.sin(self.value_rad)
90
+
91
+ def cos(self):
92
+ return math.cos(self.value_rad)
@@ -0,0 +1,47 @@
1
+ import math
2
+
3
+ from surveytools.angle import Angle
4
+
5
+
6
+ class Coordinate(object):
7
+
8
+ def __init__(self, pkt_nr : str, x: float, y: float, z: float):
9
+ self.value_rad = None
10
+ self.pkt_nr = pkt_nr
11
+ self.x = x
12
+ self.y = y
13
+ self.z = z
14
+
15
+ def __add__(self, o):
16
+ if isinstance(o, Coordinate):
17
+ return Coordinate(self.pkt_nr, self.x + o.x, self.y + o.y, self.z + o.z)
18
+ else:
19
+ raise TypeError("unsupported operand type(s) for +: 'Coordinate' and '" + type(o).__name__ + "'")
20
+
21
+ def __sub__(self, o):
22
+ if isinstance(o, Coordinate):
23
+ return Coordinate(self.pkt_nr, self.x - o.x, self.y - o.y, self.z - o.z)
24
+ else:
25
+ raise TypeError("unsupported operand type(s) for -: 'Coordinate' and '" + type(o).__name__ + "'")
26
+
27
+ def direction_to(self, o):
28
+ dc = o - self
29
+ theta_rad = math.atan2(dc.x, dc.y)
30
+ return Angle.from_rad(theta_rad)
31
+
32
+ def distance_to(self, o):
33
+ dc = o - self
34
+ return math.sqrt(dc.x * dc.x + dc.y * dc.y + dc.z * dc.z)
35
+
36
+ def get_array(self):
37
+ return [self.y, self.x, self.z]
38
+
39
+ def get_homogene_array(self):
40
+ return [self.y, self.x, self.z, 1]
41
+
42
+ def get_normalized_vector(self):
43
+ norm = math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
44
+ return [self.x / norm, self.y / norm, self.z / norm]
45
+
46
+ def __str__(self):
47
+ return f"[Pkt: {self.pkt_nr}, x: {self.x:.4f}, y: {self.y:.4f}, z: {self.z:.4f}]"
@@ -0,0 +1,20 @@
1
+ from surveytools.angle import Angle
2
+
3
+
4
+ class FullAngleMeasurement(object):
5
+
6
+ def __init__(self):
7
+ self.hz: Angle = Angle()
8
+ self.v: Angle = Angle()
9
+ self.angle_accuracy: Angle = Angle()
10
+ self.angle_time: int = 0
11
+ self.cross_incline: Angle = Angle()
12
+ self.length_incline: Angle = Angle()
13
+ self.accuracy_incline: Angle = Angle()
14
+ self.incline_time: int = 0
15
+ self.face_def: int = 0
16
+
17
+ def __str__(self):
18
+ return "[hz: " + str(self.hz) + ", v: " + str(self.v) + " , angle_accuracy: " + str(self.angle_accuracy) + \
19
+ ", angle_time: " + str(self.angle_time) + ", cross_incline: " + str(self.cross_incline) +\
20
+ ", length_incline: " + str(self.length_incline) + ", accuracy_incline: " + str(self.accuracy_incline) + "]"
@@ -0,0 +1,18 @@
1
+ from typing import List
2
+
3
+ from surveytools import MeasurementTarget, Measurement, Angle
4
+
5
+
6
+ class InstrumentStation:
7
+
8
+ def __init__(self, station_name: str):
9
+ self.station_name = station_name
10
+ self.targets : dict[str, MeasurementTarget] = dict()
11
+ self.orientation : Angle = Angle.from_rad(0)
12
+
13
+ def add_measurement_target(self, target : MeasurementTarget):
14
+ self.targets[target.target_number] = target
15
+
16
+ def evaluation(self) -> List[Measurement]:
17
+ return [x.evaluation(self.orientation) for x in self.targets.values()]
18
+
@@ -0,0 +1,34 @@
1
+ from surveytools.coordinate import Coordinate
2
+ from surveytools.angle import Angle
3
+
4
+
5
+ class Measurement(object):
6
+
7
+ def __init__(self, target : str, direction: Angle, zenith: Angle, sd : float, atmospheric_data, measure_time):
8
+ self.target_number = target
9
+ self.direction = direction
10
+ self.zenith = zenith
11
+ self.slope_distances = sd
12
+ self.atmospheric_data = atmospheric_data
13
+ self.measure_time = measure_time
14
+
15
+ def __str__(self):
16
+ return "[target: " + str(self.target_number) + ", direction: " + str(self.direction) + " , zenith: " \
17
+ + str(self.zenith) + ", slope_distances: " + str(self.slope_distances) + ", atmospheric_data: "\
18
+ + str(self.atmospheric_data) + ", Measure Time: " + str(self.measure_time) + "]"
19
+
20
+ def get_horizontal_distances(self):
21
+ return self.slope_distances * self.zenith.sin()
22
+
23
+ def get_delta_height(self):
24
+ return self.slope_distances * self.zenith.cos()
25
+
26
+ def get_local_coordinate(self):
27
+ hd = self.get_horizontal_distances()
28
+ y = hd * self.direction.sin()
29
+ x = hd * self.direction.cos()
30
+ z = self.get_delta_height()
31
+ return Coordinate(self.target_number, x, y, z)
32
+
33
+ def get_arra(self):
34
+ return [self.target_number, self.direction.get_gon(), self.zenith.get_gon(), self.slope_distances ]
@@ -0,0 +1,36 @@
1
+ from surveytools.angle import Angle
2
+ from surveytools.measurement import Measurement
3
+
4
+
5
+ class MeasurementTarget(object):
6
+ def __init__(self, target: str, direction: Angle, zenith: Angle, distance: float):
7
+ self.target_number = target
8
+ self.direction = direction
9
+ self.distance = distance
10
+ self.zenith = zenith
11
+ self.face1 = []
12
+ self.face2 = []
13
+
14
+ def evaluation(self, zero_direction: Angle = Angle.from_rad(0)) -> Measurement:
15
+ return Measurement(self.target_number, self.direction_evaluation(zero_direction), self.zenith_evaluation(), self.distance_evaluation(), self.face1[0].atmospheric_data, self.face1[0].measure_time)
16
+
17
+ def direction_evaluation(self, zero_direction : Angle) -> Angle:
18
+ sum_r = Angle()
19
+ for i in range(len(self.face1)):
20
+ sum_r += ((self.face1[i].direction + self.face2[i].direction.add_half_circle())/2.0 - zero_direction).normalise()
21
+ return sum_r/len(self.face1)
22
+
23
+ def zenith_evaluation(self) -> Angle:
24
+ sum_z = Angle()
25
+ for i in range(len(self.face1)):
26
+ sum_z += (((self.face1[i].zenith - self.face2[i].zenith).normalise()) / 2.0)
27
+ return sum_z / len(self.face1)
28
+
29
+ def distance_evaluation(self) -> float:
30
+ sum_d = 0
31
+ for i in range(len(self.face1)):
32
+ sum_d += (self.face1[i].slope_distances + self.face2[i].slope_distances) / 2.0
33
+ return sum_d / len(self.face1)
34
+
35
+ def __str__(self):
36
+ return "Target: " + str(self.target_number) + " hz: " + str(self.direction) + " vz: " + str(self.zenith) + " s: " + str(self.distance)
@@ -0,0 +1,10 @@
1
+
2
+ class SWVersion(object):
3
+
4
+ def __init__(self, release: int, version: int, sub_version: int):
5
+ self.release = release
6
+ self.version = version
7
+ self.sub_version = sub_version
8
+
9
+ def __str__(self):
10
+ return "Version {0:02d}.{1:02d}.{2:02d}".format(self.release, self.version, self.sub_version)
@@ -0,0 +1,126 @@
1
+ import math
2
+ import pytest
3
+ from surveytools import RHO, Angle
4
+
5
+
6
+ @pytest.mark.parametrize("gon", [0, 50, 200])
7
+ def test_from_and_to_gon(gon):
8
+ a = Angle.from_gon(gon)
9
+ assert pytest.approx(a.get_gon(), rel=1e-9) == gon
10
+
11
+ def test_conversion_rad_gon():
12
+ rad = math.pi
13
+ a = Angle.from_rad(rad)
14
+ assert pytest.approx(a.get_gon(), rel=1e-9) == rad * RHO
15
+
16
+ def test_normalise_wraps():
17
+ a = Angle.from_rad(3 * math.pi)
18
+ a.normalise()
19
+ assert pytest.approx(a.value_rad, rel=1e-9) == math.pi
20
+
21
+ @pytest.mark.parametrize("a_gon,b_gon,expected", [
22
+ (100, 100, True),
23
+ ( 50,100, False),
24
+ ])
25
+ def test_eq(a_gon, b_gon, expected):
26
+ a = Angle.from_gon(a_gon)
27
+ b = Angle.from_gon(b_gon)
28
+ assert (a == b) is expected
29
+ assert (a != b) is (not expected)
30
+
31
+ @pytest.mark.parametrize("a_rad,b_rad,expected", [
32
+ (1.0, 2.0, True),
33
+ (2.0, 1.0, False),
34
+ ( math.pi, math.pi, False),
35
+ ])
36
+ def test_lt(a_rad, b_rad, expected):
37
+ a = Angle.from_rad(a_rad)
38
+ b = Angle.from_rad(b_rad)
39
+ assert (a < b) is expected
40
+ assert (b > a) is expected # test __gt__
41
+
42
+ @pytest.mark.parametrize("a_rad,b_rad,expected", [
43
+ (1.0, 1.0, True),
44
+ (1.0, 2.0, True),
45
+ (2.0, 1.0, False),
46
+ ])
47
+ def test_le(a_rad, b_rad, expected):
48
+ a = Angle.from_rad(a_rad)
49
+ b = Angle.from_rad(b_rad)
50
+ assert (a <= b) is expected
51
+ assert (b >= a) is expected # test __ge__
52
+
53
+ def test_sin_cos():
54
+ # sin und cos für 30° (π/6)
55
+ a = Angle.from_rad(math.pi / 6)
56
+ assert pytest.approx(a.sin(), rel=1e-9) == 0.5
57
+ assert pytest.approx(a.cos(), rel=1e-9) == math.sqrt(3) / 2
58
+
59
+ def test_supplementary_angle():
60
+ # 2π – π/2 = 3π/2
61
+ a = Angle.from_rad(math.pi / 2)
62
+ sup = a.supplementary_angle()
63
+ assert pytest.approx(sup.value_rad, rel=1e-9) == 2 * math.pi - math.pi / 2
64
+
65
+ def test_add_half():
66
+ a = Angle.from_rad(1.0)
67
+ half = a.add_half_circle()
68
+ # prüfe reinen Wert
69
+ assert pytest.approx(half.value_rad, rel=1e-9) == 1.0 + math.pi
70
+
71
+
72
+ def test_add_and_subtract_angles():
73
+ a = Angle.from_rad(1.5)
74
+ b = Angle.from_rad(2.5)
75
+ c = a + b
76
+ d = b - a
77
+
78
+ assert pytest.approx(c.value_rad, rel=1e-9) == 4.0
79
+ assert pytest.approx(d.value_rad, rel=1e-9) == 1.0
80
+
81
+ def test_division_by_scalar():
82
+ a = Angle.from_rad(2.0)
83
+ half = a / 2
84
+ assert pytest.approx(half.value_rad, rel=1e-9) == 1.0
85
+
86
+ def test_arithmetic_type_errors():
87
+ a = Angle.from_gon(50)
88
+ with pytest.raises(TypeError):
89
+ _ = a + 5
90
+ with pytest.raises(TypeError):
91
+ _ = a - "string"
92
+ with pytest.raises(TypeError):
93
+ _ = a / Angle.from_rad(1.0)
94
+
95
+ @pytest.mark.parametrize("rad, expected", [
96
+ ( 1.0, 1.0),
97
+ (-1.0, 1.0),
98
+ ( 3*math.pi, 3*math.pi), # Betrag vor Normalisierung
99
+ (-3*math.pi, 3*math.pi),
100
+ ])
101
+ def test_abs_without_normalise(rad, expected):
102
+ """
103
+ abs() soll den ungeklärten Betrag liefern,
104
+ also abs(value_rad), ohne automatisch zu normieren.
105
+ """
106
+ a = Angle.from_rad(rad)
107
+ b = abs(a)
108
+ assert pytest.approx(b.value_rad, rel=1e-9) == expected
109
+
110
+ def test_abs_and_normalise_chain():
111
+ """
112
+ Wenn man danach normalisiert, verhält es sich erwartbar:
113
+ """
114
+ a = Angle.from_rad(-1.0)
115
+ b = abs(a)
116
+ b.normalise()
117
+ # -1rad → Betrag 1rad → normalize bleibt 1rad
118
+ assert pytest.approx(b.value_rad, rel=1e-9) == 1.0
119
+
120
+ def test_abs_type_error():
121
+ """
122
+ abs() auf Nicht-Angle-Objekt ist nicht relevant – Python ruft nur __abs__ auf Angle-Instanzen.
123
+ """
124
+ with pytest.raises(TypeError):
125
+ # falls jemand fälschlich versucht, abs mit einem anderen Typ zu tricksen
126
+ _ = Angle.from_rad(1.0).__abs__("invalid")
@@ -0,0 +1,66 @@
1
+ # tests/test_coordinate.py
2
+
3
+ import math
4
+ import pytest
5
+ from surveytools import Angle, Coordinate
6
+
7
+
8
+ def test_add_and_subtract_coordinates():
9
+ a = Coordinate("A", 1.0, 2.0, 3.0)
10
+ b = Coordinate("B", 4.0, 5.0, 6.0)
11
+ # Addition
12
+ c = a + b
13
+ assert isinstance(c, Coordinate)
14
+ assert c.pkt_nr == "A"
15
+ assert c.x == pytest.approx(5.0)
16
+ assert c.y == pytest.approx(7.0)
17
+ assert c.z == pytest.approx(9.0)
18
+ # Subtraktion
19
+ d = b - a
20
+ assert isinstance(d, Coordinate)
21
+ assert d.pkt_nr == "B"
22
+ assert d.x == pytest.approx(3.0)
23
+ assert d.y == pytest.approx(3.0)
24
+ assert d.z == pytest.approx(3.0)
25
+ # Falsche Operanden-Typen werfen TypeError
26
+ with pytest.raises(TypeError):
27
+ _ = a + 123
28
+ with pytest.raises(TypeError):
29
+ _ = b - "foo"
30
+
31
+ def test_direction_to_and_distance_to():
32
+ origin = Coordinate("O", 0.0, 0.0, 0.0)
33
+ # Punkt auf der X-Achse
34
+ p1 = Coordinate("P1", 1.0, 0.0, 0.0)
35
+ ang = origin.direction_to(p1)
36
+ # math.atan2(dc.x, dc.y) für dc=(1,0,0) ergibt π/2
37
+ assert isinstance(ang, Angle)
38
+ assert ang.value_rad == pytest.approx(math.pi/2)
39
+ # Abstand sollte 1.0 sein
40
+ assert origin.distance_to(p1) == pytest.approx(1.0)
41
+ # 3–4–12–Triangle: sqrt(9+16+144) = 13
42
+ p2 = Coordinate("P2", 3.0, 4.0, 12.0)
43
+ assert origin.distance_to(p2) == pytest.approx(13.0)
44
+
45
+ def test_array_methods_and_normalized_vector():
46
+ p = Coordinate("P", 1.1, 2.2, 3.3)
47
+ # get_array gibt [y, x, z]
48
+ assert p.get_array() == [2.2, 1.1, 3.3]
49
+ # homogene Koordinaten
50
+ assert p.get_homogene_array() == [2.2, 1.1, 3.3, 1]
51
+ # Normalisierter Vektor
52
+ norm = math.sqrt(1.1**2 + 2.2**2 + 3.3**2)
53
+ vec = p.get_normalized_vector()
54
+ assert vec[0] == pytest.approx(1.1 / norm)
55
+ assert vec[1] == pytest.approx(2.2 / norm)
56
+ assert vec[2] == pytest.approx(3.3 / norm)
57
+
58
+ def test_str_representation():
59
+ # Die __str__-Methode solltePkt-Nummer und gerundete Werte anzeigen
60
+ p = Coordinate("X1", 1.23456, 2.34567, 3.45678)
61
+ s = str(p)
62
+ assert s.startswith("[Pkt: X1")
63
+ # Je nach implementiertem Format auf 4 Dezimalstellen gerundet
64
+ assert "x: 1.2346" in s
65
+ assert "y: 2.3457" in s
66
+ assert "z: 3.4568" in s
@@ -0,0 +1,72 @@
1
+ # tests/test_measurement.py
2
+
3
+ import math
4
+ import pytest
5
+ from surveytools import Angle, Coordinate, Measurement
6
+
7
+
8
+ def test_init_and_attributes():
9
+ atm = {"pressure": 1013.25, "temp": 20.0}
10
+ mt = "2025-05-26T12:00:00"
11
+ m = Measurement(42, Angle.from_gon(100), Angle.from_rad(math.pi / 4), 123.45, atm, mt)
12
+
13
+ # Attribute wurden korrekt gesetzt
14
+ assert m.target_number == 42
15
+ assert m.direction.get_gon() == pytest.approx(100) # Gauge aus Gon :contentReference[oaicite:0]{index=0}
16
+ assert m.zenith.value_rad == pytest.approx(
17
+ math.pi / 4) # Radiant direkt aus from_rad :contentReference[oaicite:1]{index=1}
18
+ assert m.slope_distances == pytest.approx(123.45)
19
+ assert m.atmospheric_data is atm
20
+ assert m.measure_time == mt
21
+
22
+
23
+ def test_get_horizontal_distances():
24
+ # hd = slope_distances * sin(zenith)
25
+ zen = Angle.from_rad(math.pi / 6)
26
+ m = Measurement(0, Angle.from_rad(0), zen, 10.0, None, None)
27
+ expected = 10.0 * math.sin(math.pi / 6)
28
+ assert m.get_horizontal_distances() == pytest.approx(
29
+ expected) # get_horizontal_distances :contentReference[oaicite:2]{index=2}
30
+
31
+
32
+ def test_get_delta_height():
33
+ # dh = slope_distances * cos(zenith)
34
+ zen = Angle.from_rad(math.pi / 3)
35
+ m = Measurement(0, Angle.from_rad(0), zen, 20.0, None, None)
36
+ expected = 20.0 * math.cos(math.pi / 3)
37
+ assert m.get_delta_height() == pytest.approx(expected) # get_delta_height :contentReference[oaicite:3]{index=3}
38
+
39
+
40
+ def test_get_local_coordinate():
41
+ # hd = sd * sin(zenith) = 5 * 1 = 5
42
+ # direction = 45° → x = hd*cos(45°), y = hd*sin(45°), z = sd*cos(zenith) = 0
43
+ dir_ang = Angle.from_rad(math.pi / 4)
44
+ zen = Angle.from_rad(math.pi / 2)
45
+ m = Measurement(7, dir_ang, zen, 5.0, None, None)
46
+ coord = m.get_local_coordinate()
47
+
48
+ assert isinstance(coord, Coordinate)
49
+ assert coord.pkt_nr == 7
50
+ assert coord.x == pytest.approx(
51
+ 5.0 * math.cos(math.pi / 4)) # get_local_coordinate :contentReference[oaicite:4]{index=4}
52
+ assert coord.y == pytest.approx(5.0 * math.sin(math.pi / 4))
53
+ assert coord.z == pytest.approx(5.0 * math.cos(math.pi / 2))
54
+
55
+
56
+ def test_get_arra_and_str():
57
+ # get_arra liefert [target_number, direction_gon, zenith_gon, slope_distances]
58
+ dir_ang = Angle.from_gon(150)
59
+ zen = Angle.from_gon(75)
60
+ m = Measurement(8, dir_ang, zen, 12.3, "ATM_DATA", "TIME_STAMP")
61
+ arr = m.get_arra()
62
+ assert arr == [8, pytest.approx(dir_ang.get_gon()), pytest.approx(zen.get_gon()),
63
+ pytest.approx(12.3)] # get_arra :contentReference[oaicite:5]{index=5}
64
+
65
+ # __str__ enthält alle Informationen
66
+ s = str(m)
67
+ assert "target: 8" in s
68
+ assert "direction:" in s and "gon" in s
69
+ assert "zenith:" in s and "gon" in s
70
+ assert "slope_distances: 12.3" in s
71
+ assert "atmospheric_data: ATM_DATA" in s
72
+ assert "Measure Time: TIME_STAMP" in s # __str__ :contentReference[oaicite:6]{index=6}
@@ -0,0 +1,66 @@
1
+ import pytest
2
+
3
+ from surveytools import MeasurementTarget, Measurement, Angle
4
+
5
+ # 1. Erstelle jeweils zwei Measurement-Instanzen für face1
6
+ m1_f1 = Measurement(
7
+ target=1,
8
+ direction=Angle.from_gon(172.1609), # Horizontalwinkel in Gon
9
+ zenith=Angle.from_gon(97.1722), # Zenitwinkel in Gon
10
+ sd=12.287, # Schrägentfernung
11
+ atmospheric_data={"pressure": 1013.25},
12
+ measure_time="2025-05-26T10:00:00"
13
+ )
14
+ m2_f1 = Measurement(
15
+ target=1,
16
+ direction=Angle.from_gon(172.1659),
17
+ zenith=Angle.from_gon(97.1682),
18
+ sd=12.29,
19
+ atmospheric_data={"pressure": 1013.25},
20
+ measure_time="2025-05-26T10:01:00"
21
+ )
22
+
23
+ # 2. Erstelle jeweils zwei Measurement-Instanzen für face2
24
+ m1_f2 = Measurement(
25
+ target=1,
26
+ direction=Angle.from_gon(372.1589),
27
+ zenith=Angle.from_gon(302.8283),
28
+ sd=12.292,
29
+ atmospheric_data={"pressure": 1013.25},
30
+ measure_time="2025-05-26T10:02:00"
31
+ )
32
+ m2_f2 = Measurement(
33
+ target=1,
34
+ direction=Angle.from_gon(372.1589),
35
+ zenith=Angle.from_gon(302.8322),
36
+ sd=12.285,
37
+ atmospheric_data={"pressure": 1013.25},
38
+ measure_time="2025-05-26T10:03:00"
39
+ )
40
+
41
+ # 3. Instanziiere das MeasurementTarget
42
+ target = MeasurementTarget(
43
+ target=1,
44
+ direction=Angle.from_gon(172), # Referenzrichtung
45
+ zenith=Angle.from_gon(97), # Referenzzenit
46
+ distance=12.3 # Referenzentfernung
47
+ ) # face1 und face2 sind zunächst leere Listen
48
+
49
+ # 4. Fülle face1 und face2
50
+ target.face1.extend([m1_f1, m2_f1])
51
+ target.face2.extend([m1_f2, m2_f2])
52
+
53
+ # 5. Optional: Auswertung durchführen
54
+ result = target.evaluation(zero_direction=Angle.from_rad(0))
55
+
56
+
57
+ def test_measurement_target_evaluation_practical():
58
+ # 4) Auswertung prüfen
59
+ # Direction: (172.1609 + 172.1659 + (372.1589-200)*2) / 4 ≈ 172.1612 gon
60
+ assert result.direction.get_gon() == pytest.approx(172.1612, abs=1e-4)
61
+
62
+ # Zenith: (97.1722 + 97.1682 + (400-302.8283) + (400-302.8322)) / 4 ≈ 97.1700 gon
63
+ assert result.zenith.get_gon() == pytest.approx(97.1700, abs=1e-4)
64
+
65
+ # Slope distance: (12.287 + 12.290 + 12.292 + 12.285) / 4 ≈ 12.2885
66
+ assert result.slope_distances == pytest.approx(12.2885, abs=1e-4)