dms2dd and dd2dms - fix doc-strings, refactor, add tests (#6763)

This commit is contained in:
falken10vdl
2025-09-04 13:55:13 +02:00
committed by GitHub
parent d4918edcd9
commit 5304072892
3 changed files with 66 additions and 24 deletions
@@ -23,6 +23,7 @@ import ifcopenshell.util.unit
import ifcopenshell.util.element
import ifcopenshell.util.placement
from typing import NamedTuple, Optional, Union
from decimal import Decimal, ROUND_HALF_UP
MatrixType = ifcopenshell.util.placement.MatrixType
@@ -39,39 +40,54 @@ class HelmertTransformation(NamedTuple):
factor_z: float
def dms2dd(degrees: int, minutes: int, seconds: int, ms: int = 0) -> float:
"""Convert degrees, minutes, and (milli)seconds to decimal degrees
def dms2dd(degrees: int, minutes: int, seconds: int, us: int = 0) -> float:
"""Convert degrees, minutes, and (micro)seconds to decimal degrees
All components must be either positive or negative.
:param degrees: The degrees component
:param minutes: The minutes component
:param seconds: The seconds component
:param ms: The milliseconds component
:param us: The microseconds component
:return: The angle in decimal degrees.
"""
dd = float(degrees) + float(minutes) / 60.0 + float(seconds) / (3600.0) + float(ms / 3600000000.0)
return dd
all_positive_or_zero = degrees >= 0 and minutes >= 0 and seconds >= 0 and us >= 0
all_negative_or_zero = degrees <= 0 and minutes <= 0 and seconds <= 0 and us <= 0
assert all_positive_or_zero or all_negative_or_zero
return degrees + minutes / 60.0 + seconds / 3600.0 + us / 3600000000.0
def dd2dms(dd: float, use_ms: bool = False) -> Union[tuple[float, float, float, float], tuple[float, float, float]]:
"""Convert decimal degrees to degrees, minutes, and (milli)seconds format
def dd2dms(dd: float, use_us: bool = False) -> Union[tuple[int, int, int, int], tuple[int, int, float]]:
"""Convert decimal degrees to degrees, minutes, and (micro)seconds format
:param dd: The decimal degrees
:param use_ms: True if to include milliseconds and false otherwise. Defaults to false.
:return: The angle in a tuple of either 3 or 4 values, being degrees,
minutes, seconds, and optionally milliseconds.
:param use_us: True if to include microseconds and false otherwise. Defaults to false.
:return: The angle in a tuple of either 3 or 4 values,
4 values: integer number of degrees, integer number of minutes, integer number of seconds and integer number of microseconds
3 values: integer number of degrees, integer number of minutes, and a float number for seconds
:note: the tuple follows the format of IfcCompoundPlaneAngleMeasure. Namely all of its components are either positive or negative.
"""
dd = float(dd)
sign = 1 if dd >= 0 else -1
dd = abs(dd)
if use_ms:
seconds, ms = divmod(dd * 60 * 60 * 1000000, 1000000)
minutes, seconds = divmod(dd * 60 * 60, 60)
degrees, minutes = divmod(minutes, 60)
if dd < 0:
degrees = -degrees
if use_ms:
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign, int(ms) * sign)
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign)
dd_decimal = Decimal(str(dd))
degrees = int(dd_decimal)
degrees_decimal = Decimal(degrees)
fractional_part = dd_decimal - degrees_decimal
minutes_decimal = fractional_part * Decimal(60)
minutes = int(minutes_decimal)
minutes_decimal_int = Decimal(minutes)
seconds_decimal = (minutes_decimal - minutes_decimal_int) * Decimal(60)
if use_us:
seconds = int(seconds_decimal)
seconds_decimal_int = Decimal(seconds)
microseconds_decimal = (seconds_decimal - seconds_decimal_int) * Decimal(1000000)
microseconds = int(microseconds_decimal.quantize(Decimal(1), rounding=ROUND_HALF_UP))
return (degrees, minutes, seconds, microseconds)
else:
seconds_float = float(seconds_decimal)
return (degrees, minutes, seconds_float)
def xyz2enh(
@@ -468,3 +468,29 @@ class TestAngle2YAxis(test.bootstrap.IFC4):
assert np.allclose(subject.angle2yaxis(45), (-a, a))
assert np.allclose(subject.angle2yaxis(-135), (a, -a))
assert np.allclose(subject.angle2yaxis(135), (-a, -a))
class TestDMS2DDandDD2DMS(test.bootstrap.IFC4):
def test_dms2dd_and_dd2dms(self):
test_cases_3tuple = [
(35.41, (35, 24, 36.0)),
(-116.89, (-116, -53, -24.0)),
]
test_cases_4tuple = [
(40.431389, (40, 25, 53, 400)),
(-4.248056, (-4, -14, -53, -1600)),
(-35.401389, (-35, -24, -5, -400)),
(148.981667, (148, 58, 54, 1200)),
]
for dd, dms in test_cases_3tuple:
d, m, s = subject.dd2dms(dd)
assert (d, m, s) == dms
dd_converted = subject.dms2dd(dms[0], dms[1], dms[2])
assert dd_converted == dd
for dd, dms in test_cases_4tuple:
d, m, s, us = subject.dd2dms(dd, use_us=True)
assert (d, m, s, us) == dms
dd_converted = subject.dms2dd(dms[0], dms[1], dms[2], dms[3])
assert dd_converted == dd