From 308751d9566f3928b0ce2061e6c74b6368278f69 Mon Sep 17 00:00:00 2001 From: jonnymaserati Date: Wed, 20 Dec 2023 01:09:11 +0100 Subject: [PATCH] Added a couple of functions to for converting between decimal and degrees, minutes and seconds coordinates. --- tests/test_utils.py | 56 ++++++++++++++++++++++++++++++- welleng/utils.py | 82 +++++++++++++++++++++++++++++++++++++++++++++ welleng/version.py | 2 +- 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index abd758c..78b39b1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,8 +1,23 @@ import inspect +import numpy as np +from numpy.typing import NDArray import sys from welleng.units import ureg -from welleng.utils import annular_volume +from welleng.utils import annular_volume, decimal2dms, dms2decimal + + +LAT, LON = (52, 4, 43.1868), (4, 17, 19.6368) + + +def _generate_random_dms(n: int) -> NDArray: + """Generates a bunch of lat, lon coordinates. + """ + deg = np.random.randint(-180, 180, n) + min = np.random.randint(0, 60, n) + sec = np.random.uniform(0, 60, n) + + return np.stack((deg, min, sec), axis=-1).reshape((-1, 2, 3)) def test_annular_volume(): @@ -18,6 +33,45 @@ def test_annular_volume(): pass +def test_decimal2dms(): + degrees, minutes, seconds = decimal2dms(LAT[0] + LAT[1] / 60 + LAT[2] / 3600) + assert (degrees, minutes, round(seconds, 4)) == LAT + + dms = decimal2dms(np.array([ + -(LAT[0] + LAT[1] / 60 + LAT[2] / 3600), + LON[0] + LON[1] / 60 + LON[2] / 3600 + ])) + assert np.allclose( + dms, + np.array((np.array(LAT) * np.array((-1, 1, 1)), np.array(LON))) + ) + + +def test_deg2decimal(): + decimal = dms2decimal((-LAT[0], LAT[1], LAT[2])) # check it handles westerly + assert decimal == -(LAT[0] + LAT[1] / 60 + LAT[2] / 3600) + + decimals = dms2decimal((LAT, LON)) + assert np.allclose(decimals, np.array((dms2decimal(LAT), dms2decimal(LON)))) + + decimals = dms2decimal(((LAT, LON), (LON, LAT))) + assert np.allclose( + decimals, + np.array(( + (dms2decimal(LAT), dms2decimal(LON)), + (dms2decimal(LON), dms2decimal(LAT)) + )) + ) + + +def test_dms2decimal2dms(): + _dms = _generate_random_dms(int(1e3)) + decimal = dms2decimal(_dms) + dms = decimal2dms(decimal) + + assert np.allclose(_dms, dms) + + def one_function_to_run_them_all(): """ Function to gather the test functions so that they can be tested by diff --git a/welleng/utils.py b/welleng/utils.py index 35ee6c4..ffaeb73 100644 --- a/welleng/utils.py +++ b/welleng/utils.py @@ -1,6 +1,7 @@ from typing import Annotated, Literal, Union import numpy as np +from numpy.exceptions import AxisError from numpy.typing import NDArray from scipy.spatial.transform import Rotation as R @@ -686,3 +687,84 @@ def annular_volume(od: float, id: float = None, length: float = None): annular_volume = annular_unit_volume * length return annular_volume + + +@np.vectorize(signature='()->(n)') +def _decimal2dms(decimal: float) -> tuple: + minutes, seconds = divmod(abs(decimal) * 3600, 60) + _, minutes = divmod(minutes, 60) + return np.array([int(decimal), minutes, seconds]) + + +def decimal2dms(decimal: float | tuple) -> tuple | NDArray: + """Converts a decimal lat, lon to degrees, minutes and seconds. + + Parameters + ---------- + decimal : float | arraylike + A decimal lat or lon or arraylike of lat, lon coordinates. + + Returns + ------- + dms: tuple | arraylike + A tuple or array of (degrees, minutes, seconds). + + Examples + -------- + If you want to convert the lat/lon coordinates for Den Haag from decimals + to degrees, minutes and seconds: + + >>> LAT, LON = [52.078663000000006, 4.288788] + >>> dms = decimal2dms((LAT, LON)) + >>> print(dms) + [[52. 4. 43.1868] + [ 4. 17. 19.6368]] + """ + dms = _decimal2dms(decimal) + + if dms.shape == (3,): + return tuple(dms) + else: + return dms + + +def _dms2decimal(dms: tuple) -> float: + degrees, minutes, seconds = dms + result = abs(degrees) + minutes / 60 + seconds / 3600 + + if degrees == 0: # avoid divide by zero errors + return result + else: + return result * abs(degrees) / degrees + + +def dms2decimal(dms: tuple | NDArray) -> float | NDArray: + """Converts a degrees, minutes and seconds lat, lon to decimals. + + Parameters + ---------- + dms : tuple | arraylike + A tuple or arraylike of (degrees, minutes, seconds) lat and/or lon or + arraylike of lat, lon coordinates. + + Returns + ------- + degrees: tuple | arraylike + A tuple or array of lats and/or longs in decimals. + + Examples + -------- + If you want to convert the lat/lon coordinates for Den Haag from degrees, + minutes and seconds to decimals: + + >>> LAT, LON = (52, 4, 43.1868), (4, 17, 19.6368) + >>> decimal = dms2decimal((LAT, LON)) + >>> print(decimal) + [52.078663 4.288788] + """ + result = np.apply_along_axis(_dms2decimal, -1, np.array(dms)) + + if result.shape == (): + return float(result) + else: + return result diff --git a/welleng/version.py b/welleng/version.py index 79866ab..b8308a1 100644 --- a/welleng/version.py +++ b/welleng/version.py @@ -1 +1 @@ -__version__ = '0.7.6' +__version__ = '0.7.7'