From e5b0dec161c98a6b3d8d46759220e8ab09263116 Mon Sep 17 00:00:00 2001
From: Amy Stamile <astamile@usgs.gov>
Date: Fri, 19 Apr 2024 12:44:56 -0700
Subject: [PATCH] Added docs and classes

---
 knoten/utils.py     | 155 +++++++++++++++++++++++++++++++++++++++-----
 tests/test_utils.py |  92 +++++++++++++-------------
 2 files changed, 187 insertions(+), 60 deletions(-)

diff --git a/knoten/utils.py b/knoten/utils.py
index 5920e38..0a69fd6 100644
--- a/knoten/utils.py
+++ b/knoten/utils.py
@@ -1,11 +1,39 @@
 import pyproj
 import numpy as np 
-from collections import namedtuple
+# from collections import namedtuple
+from typing import NamedTuple
 
-def sep_angle(a_pt, b_pt, c_pt):
-    return sep_angle(a_pt - b_pt, c_pt - b_pt)
+class Point(NamedTuple):
+    x: float
+    y: float
+    z: float
+
+class LatLon(NamedTuple):
+    lat: float
+    lon: float
+
+class Sphere(NamedTuple):
+    lat: float
+    lon: float
+    radius: float
+
+class Matrix(NamedTuple):
+    vec_a: Point
+    vec_b: Point
+    vec_c: Point
 
 def sep_angle(a_vec, b_vec):
+    """
+    Parameters
+    ----------
+    a_vec : Point object (x, y, z)
+
+    b_vec : Point object (x, y, z)
+
+    Returns
+    -------
+    : float
+    """
     dot_prod = a_vec.x * b_vec.x + a_vec.y * b_vec.y + a_vec.z * b_vec.z
     dot_prod /= magnitude(a_vec) * magnitude(b_vec)
 
@@ -15,17 +43,43 @@ def sep_angle(a_vec, b_vec):
     return np.arccos(dot_prod)
 
 def magnitude(vec):
+    """
+    Parameters
+    ----------
+    vec : Point object (x, y, z)
+
+    Returns
+    -------
+    : float
+    """
     return np.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z)
 
 def distance(start, stop):
-    Point = namedtuple("Point", 'x, y, z')
+    """
+    Parameters
+    ----------
+    start : Point object (x, y, z)
+
+    stop : Point object (x, y, z)
+
+    Returns
+    -------
+    : float
+    """
     diff = Point(stop.x - start.x, stop.y - start.y, stop.z - start.z)
 
     return magnitude(diff)
 
 def radiansToDegrees(radian_lat_lon):
-    LatLon = namedtuple("LatLon", 'lat lon')
+    """
+    Parameters
+    ----------
+    radian_lat_lon : LatLon object (lat, lon) in radians
 
+    Returns
+    -------
+    : LatLon object (lat, lon) in degrees
+    """
     degree_lon = radian_lat_lon.lon
     if (degree_lon < 0):
       degree_lon += 2 * np.pi
@@ -35,8 +89,15 @@ def radiansToDegrees(radian_lat_lon):
     return LatLon(degreeLat, degree_lon)
 
 def spherical_to_rect(spherical):
-    Point = namedtuple("Point", 'x, y, z')
+    """
+    Parameters
+    ----------
+    spherical : Sphere object (lat, lon, radius)
 
+    Returns
+    -------
+    : Point object (x, y, z)
+    """
     x = spherical.radius * np.cos(spherical.lat) * np.cos(spherical.lon)
     y = spherical.radius * np.cos(spherical.lat) * np.sin(spherical.lon)
     z = spherical.radius * np.sin(spherical.lat)
@@ -44,8 +105,15 @@ def spherical_to_rect(spherical):
     return Point(x, y, z)
 
 def rect_to_spherical(rectangular):
-    Sphere = namedtuple("Sphere", 'lat, lon, radius')
+    """
+    Parameters
+    ----------
+    rectangular : Point object (x, y, z)
 
+    Returns
+    -------
+    : Sphere object (lat, lon, radius)
+    """
     rad = magnitude(rectangular)
     if (rad < 1e-15):
       return Sphere(0.0, 0.0, 0.0)
@@ -57,8 +125,17 @@ def rect_to_spherical(rectangular):
     )
 
 def ground_azimuth(ground_pt, sub_pt):
-    LatLon = namedtuple("LatLon", 'lat lon')
+    """
+    Parameters
+    ----------
+    ground_pt : LatLon object (lat, lon)
 
+    sub_pt : LatLon object (lat, lon)
+
+    Returns
+    -------
+    : float
+    """
     if (ground_pt.lat >= 0.0):
       a = (90.0 - sub_pt.lat) * np.pi / 180.0
       b = (90.0 - ground_pt.lat) * np.pi / 180.0
@@ -128,19 +205,47 @@ def ground_azimuth(ground_pt, sub_pt):
     return azimuth
 
 def crossProduct(a_vec, b_vec):
-    Point = namedtuple("Point", 'x, y, z')
+    """
+    Parameters
+    ----------
+    a_vec : Point object (x, y, z)
+
+    b_vec : Point object (x, y, z)
 
+    Returns
+    -------
+    : Point object (x, y, z)
+    """
     x = a_vec.y * b_vec.z - a_vec.z * b_vec.y
     y = a_vec.z * b_vec.x - a_vec.x * b_vec.z
     z = a_vec.x * b_vec.y - a_vec.y * b_vec.x
     return Point(x, y, z)
 
-
 def unit_vector(vec):
+    """
+    Parameters
+    ----------
+    vec : Point object (x, y, z)
+
+    Returns
+    -------
+    : Point object (x, y, z)
+    """
     mag = magnitude(vec)
     return vec / mag
 
 def perpendicular_vector(a_vec, b_vec):
+    """
+    Parameters
+    ----------
+    a_vec : Point object (x, y, z)
+
+    b_vec : Point object (x, y, z)
+
+    Returns
+    -------
+    : Point object (x, y, z)
+    """
     if (magnitude(a_vec) == 0):
       return b_vec
 
@@ -157,16 +262,34 @@ def perpendicular_vector(a_vec, b_vec):
     return q
 
 def scale_vector(vec, scalar):
-    Point = namedtuple("Point", 'x, y, z')
+    """
+    Parameters
+    ----------
+    vec : Point object (x, y, z)
 
+    scalar : float
+
+    Returns
+    -------
+    : Point object (x, y, z)
+    """
     return Point(vec.x * scalar, vec.y * scalar, vec.z * scalar)
 
-def matrixVecProduct(mat, vec):
-    Point = namedtuple("Point", 'x, y, z')
+def matrix_vec_product(mat, vec):
+    """
+    Parameters
+    ----------
+    mat : Matrix object (vec_a, vec_b, vec_c)
+
+    vec : Point object (x, y, z)
 
-    x = mat.a.x * vec.x + mat.a.y * vec.y + mat.a.z * vec.z
-    y = mat.b.x * vec.x + mat.b.y * vec.y + mat.b.z * vec.z
-    z = mat.c.x * vec.x + mat.c.y * vec.y + mat.c.z * vec.z
+    Returns
+    -------
+    : Point object (x, y, z)
+    """
+    x = mat.vec_a.x * vec.x + mat.vec_a.y * vec.y + mat.vec_a.z * vec.z
+    y = mat.vec_b.x * vec.x + mat.vec_b.y * vec.y + mat.vec_b.z * vec.z
+    z = mat.vec_c.x * vec.x + mat.vec_c.y * vec.y + mat.vec_c.z * vec.z
 
     return Point(x, y, z)
 
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 8547c0e..d68ab18 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,100 +1,104 @@
 import numpy as np
 from knoten import utils
-from collections import namedtuple
-
-Point = namedtuple("Point", 'x, y, z')
-Sphere = namedtuple("Sphere", 'lat, lon, radius')
 
 def test_sep_angle_right():
-  pt1 = Point(1, 0, 0)
-  pt2 = Point(0, 1, 0)
+  pt1 = utils.Point(1, 0, 0)
+  pt2 = utils.Point(0, 1, 0)
   np.testing.assert_array_equal(utils.sep_angle(pt1, pt2), np.pi / 2.0)
 
 def test_sep_angle_acute():
-  pt1 = Point(1, 0, 0)
-  pt2 = Point(1, 1, 0)
+  pt1 = utils.Point(1, 0, 0)
+  pt2 = utils.Point(1, 1, 0)
   np.testing.assert_allclose(utils.sep_angle(pt1, pt2), np.pi / 4.0, atol=1e-12)
 
 def test_sep_angle_obtuse():
-  pt1 = Point(1, 0, 0)
-  pt2 = Point(-1, 1, 0)
+  pt1 = utils.Point(1, 0, 0)
+  pt2 = utils.Point(-1, 1, 0)
   np.testing.assert_array_equal(utils.sep_angle(pt1, pt2), 3.0 * np.pi / 4.0)
 
 def test_sep_angle_normalization():
-  pt1 = Point(1, 0, 0)
-  pt2 = Point(1, 1, 0)
-  pt3 = Point(100, 0, 0)
-  pt4 = Point(100, 100, 0)
+  pt1 = utils.Point(1, 0, 0)
+  pt2 = utils.Point(1, 1, 0)
+  pt3 = utils.Point(100, 0, 0)
+  pt4 = utils.Point(100, 100, 0)
   np.testing.assert_array_equal(utils.sep_angle(pt1, pt2), utils.sep_angle(pt3, pt4))
 
 def test_magnitude_unit():
-  assert utils.magnitude(Point(1.0, 0.0, 0.0)) == 1.0
-  assert utils.magnitude(Point(0.0, 1.0, 0.0)) == 1.0
-  assert utils.magnitude(Point(0.0, 0.0, 1.0)) == 1.0
+  assert utils.magnitude(utils.Point(1.0, 0.0, 0.0)) == 1.0
+  assert utils.magnitude(utils.Point(0.0, 1.0, 0.0)) == 1.0
+  assert utils.magnitude(utils.Point(0.0, 0.0, 1.0)) == 1.0
 
 def test_magnitude_nonunit():
-  assert utils.magnitude(Point(0.0, 0.0, 0.0)) == 0.0
-  assert utils.magnitude(Point(2.0, 1.0, 4.0)) == np.sqrt(21.0)
-  np.testing.assert_allclose(utils.magnitude(Point(0.2, 0.1, 0.4)), np.sqrt(0.21), atol=1e-12)
+  assert utils.magnitude(utils.Point(0.0, 0.0, 0.0)) == 0.0
+  assert utils.magnitude(utils.Point(2.0, 1.0, 4.0)) == np.sqrt(21.0)
+  np.testing.assert_allclose(utils.magnitude(utils.Point(0.2, 0.1, 0.4)), np.sqrt(0.21), atol=1e-12)
 
 def test_distance():
-  assert utils.distance(Point(1.0, 2.0, 3.0), Point(6.0, 5.0, 4.0)) == np.sqrt(35)
+  assert utils.distance(utils.Point(1.0, 2.0, 3.0), utils.Point(6.0, 5.0, 4.0)) == np.sqrt(35)
 
 def test_spherical_to_rect():
-  result = utils.spherical_to_rect(Sphere(0.0, 0.0, 1000.0))
+  result = utils.spherical_to_rect(utils.Sphere(0.0, 0.0, 1000.0))
   np.testing.assert_allclose(result.x, 1000.0, atol=1e-12)
   np.testing.assert_allclose(result.y, 0.0, atol=1e-12)
   np.testing.assert_allclose(result.z, 0.0, atol=1e-12)
 
-  result = utils.spherical_to_rect(Sphere(0.0, np.pi, 1000.0))
+  result = utils.spherical_to_rect(utils.Sphere(0.0, np.pi, 1000.0))
   np.testing.assert_allclose( result.x, -1000.0, atol=1e-12)
   np.testing.assert_allclose( result.y, 0.0, atol=1e-12)
   np.testing.assert_allclose( result.z, 0.0, atol=1e-12)
 
-  result = utils.spherical_to_rect(Sphere(np.pi / 2.0, 0.0, 1000.0))
+  result = utils.spherical_to_rect(utils.Sphere(np.pi / 2.0, 0.0, 1000.0))
   np.testing.assert_allclose( result.x, 0.0, atol=1e-12)
   np.testing.assert_allclose( result.y, 0.0, atol=1e-12)
   np.testing.assert_allclose( result.z, 1000.0, atol=1e-12)
 
-  result = utils.spherical_to_rect(Sphere(np.pi / -2.0, 0.0, 1000.0))
+  result = utils.spherical_to_rect(utils.Sphere(np.pi / -2.0, 0.0, 1000.0))
   np.testing.assert_allclose( result.x, 0.0, atol=1e-12)
   np.testing.assert_allclose( result.y, 0.0, atol=1e-12)
   np.testing.assert_allclose( result.z, -1000.0, atol=1e-12)
 
 def test_rect_to_spherical():
-  result = utils.rect_to_spherical(Point(1000.0, 0.0, 0.0))
-  np.testing.assert_array_equal(result, Sphere(0.0, 0.0, 1000.0))
+  result = utils.rect_to_spherical(utils.Point(1000.0, 0.0, 0.0))
+  np.testing.assert_array_equal(result, utils.Sphere(0.0, 0.0, 1000.0))
 
-  result = utils.rect_to_spherical(Point(-1000.0, 0.0, 0.0))
-  np.testing.assert_array_equal(result, Sphere(0.0, np.pi, 1000.0))
+  result = utils.rect_to_spherical(utils.Point(-1000.0, 0.0, 0.0))
+  np.testing.assert_array_equal(result, utils.Sphere(0.0, np.pi, 1000.0))
 
-  result = utils.rect_to_spherical(Point(0.0, 0.0, 1000.0))
-  np.testing.assert_array_equal(result, Sphere(np.pi / 2.0, 0.0, 1000.0))
+  result = utils.rect_to_spherical(utils.Point(0.0, 0.0, 1000.0))
+  np.testing.assert_array_equal(result, utils.Sphere(np.pi / 2.0, 0.0, 1000.0))
 
-  result = utils.rect_to_spherical(Point(0.0, 0.0, -1000.0))
-  np.testing.assert_array_equal(result,  Sphere(np.pi / -2.0, 0.0, 1000.0))
+  result = utils.rect_to_spherical(utils.Point(0.0, 0.0, -1000.0))
+  np.testing.assert_array_equal(result,  utils.Sphere(np.pi / -2.0, 0.0, 1000.0))
 
 def test_ground_azimuth():
-  LatLon = namedtuple("LatLon", "lat lon")
-
-  ground_pt = LatLon(0, -180)
-  subsolar_pt = LatLon(0, 90)
+  ground_pt = utils.LatLon(0, -180)
+  subsolar_pt = utils.LatLon(0, 90)
   np.testing.assert_array_equal(270.0, utils.ground_azimuth(ground_pt, subsolar_pt))
 
 def test_perpendicular_vector():
-  vec_a = Point(6.0, 6.0, 6.0)
-  vec_b = Point(2.0, 0.0, 0.0)
-  result = Point(0.0, 6.0, 6.0)
+  vec_a = utils.Point(6.0, 6.0, 6.0)
+  vec_b = utils.Point(2.0, 0.0, 0.0)
+  result = utils.Point(0.0, 6.0, 6.0)
   np.testing.assert_array_equal(utils.perpendicular_vector(vec_a, vec_b), result)
 
 def test_unit_vector():
-  result = utils.unit_vector(Point(5.0, 12.0, 0.0))
+  result = utils.unit_vector(utils.Point(5.0, 12.0, 0.0))
   np.testing.assert_allclose(result[0], 0.384615, atol=1e-6)
   np.testing.assert_allclose(result[1], 0.923077, atol=1e-6)
   np.testing.assert_array_equal(result[2], 0.0)
 
 def test_scale_vector():
-  vec = Point(1.0, 2.0, -3.0)
+  vec = utils.Point(1.0, 2.0, -3.0)
   scalar = 3.0
-  result = Point(3.0, 6.0, -9.0)
-  np.testing.assert_array_equal(utils.scale_vector(vec, scalar), result)
\ No newline at end of file
+  result = utils.Point(3.0, 6.0, -9.0)
+  np.testing.assert_array_equal(utils.scale_vector(vec, scalar), result)
+
+def test_matrix_vec_product():
+  vec_a = utils.Point(0.0,  1.0,  0.0)
+  vec_b = utils.Point(-1.0,  0.0,  0.0)
+  vec_c = utils.Point(0.0,  0.0,  1.0)
+  mat = utils.Matrix(vec_a, vec_b, vec_c)
+  vec = utils.Point(1.0,  2.0,  3.0)
+
+  result = utils.Point(2.0, -1.0, 3.0)
+  np.testing.assert_array_equal(result, utils.matrix_vec_product(mat, vec))
\ No newline at end of file
-- 
GitLab