Source code for planetary_coverage.math.vectors

"""Vector module.

Warning
-------
All the longitude are defined eastward.

"""

import numpy as np


[docs]def cs(angle): """Cosines and sines value of an angle (°). Parameters ---------- angle: float or numpy.ndarray Input angle(s) (°). Returns ------- float or numpy.ndarray Cosine and Sines of this angle(s). Examples -------- >>> cs(0) 1, 0 >>> cs(45) 0.707..., 0.707... """ theta = np.radians(angle) return np.cos(theta), np.sin(theta)
[docs]def norm(v): """Vector norm. Parameters ---------- v: numpy.ndarray Input vector to measure(s). Returns ------- float or numpy.ndarray Input vector norm(s). Examples -------- >>> norm([1, 0, 0]) 1 >>> norm([1, 1, 1]) 1.732050... """ return np.linalg.norm(v, axis=-1)
[docs]def hat(v): """Normalize vector. Parameters ---------- v: numpy.ndarray Input vector to normalize. Returns ------- numpy.ndarray Normalize input vector. Examples --------- >>> hat([1, 0, 0]) array([1., 0., 0.]) >>> hat([1, 1, 1]) array([0.577..., 0.577..., 0.577...]) """ n = norm(v) inv = np.divide(1, n, out=np.zeros_like(n), where=n != 0) return np.multiply(np.expand_dims(inv, axis=-1), v)
[docs]def lonlat(xyz): """Convert cartesian coordinates into geographic coordinates. Parameters ---------- xyz: numpy.ndarray XYZ cartesian vector. Return ------ (float, float) East Longitude [0°, 360°[ and North Latitude (°). Examples -------- >>> lonlat([1, 0, 0]) (0, 0) >>> lonlat([0, 1, 0]) (90, 0) >>> lonlat([1, 1, 0]) (45, 0) >>> lonlat([1, 0, 1]) (0, 45) """ (x, y, z), n = np.transpose(xyz), norm(xyz) cond = n != 0 lon_e = np.degrees(np.arctan2(y, x), where=cond, out=np.zeros_like(n)) % 360 lat = np.degrees(np.arcsin(np.divide(z, n, where=cond, out=np.zeros_like(n)))) return np.array([lon_e.T, lat.T])
[docs]def xyz(lon_e, lat, r=1): """Convert geographic coordinates in cartesian coordinates. Parameters ---------- lon_e: float or numpy.ndarray Point(s) east longitude [0°, 360°[. lat: float or numpy.ndarray Point(s) latitude [-90°, 90°]. r: float or numpy.ndarray, optional Point(s) distance/altitude [km]. Return ------ [float, float, float] Cartesian coordinates. Examples -------- >>> xyz(0, 0) [1, 0, 0] >>> xyz(90, 0) [0, 1, 0] >>> xyz(45, 0) [0.707..., 0.707..., 0] >>> xyz(0, 45) [0.707..., 0, 0.707...] """ _1d = np.ndim(lon_e) > 0 or np.ndim(lat) > 0 or np.ndim(r) > 0 _2d = np.ndim(lon_e) > 1 or np.ndim(lat) > 1 or np.ndim(r) > 1 if np.ndim(lon_e) > 0 and np.ndim(lat) == 0: lat = np.broadcast_to(lat, np.shape(lon_e)) elif np.ndim(lon_e) == 0 and np.ndim(lat) > 0: lon_e = np.broadcast_to(lon_e, np.shape(lat)) elif np.ndim(lon_e) == 0 and np.ndim(lat) == 0 and np.ndim(r) > 0: lon_e = np.broadcast_to(lon_e, np.shape(r)) lat = np.broadcast_to(lat, np.shape(r)) (clon_e, slon_e), (clat, slat) = cs(lon_e), cs(lat) v = np.multiply(r, [clon_e * clat, slon_e * clat, slat]) return np.moveaxis(v, 0, 2) if _2d else v.T if _1d else v
[docs]def vdot(v1, v2): """Dot product between two vectors.""" if np.ndim(v1) == 1 and np.ndim(v2) == 1: return np.dot(v1, v2) if np.ndim(v1) == 1: return np.dot(v2, v1) if np.ndim(v2) == 1: return np.dot(v1, v2) if np.shape(v1)[1:] == np.shape(v2)[1:]: return np.sum(np.multiply(v1, v2), axis=-1) raise ValueError('The two vectors must have the same number of points.')
[docs]def angle(v1, v2): """Angular separation between two vectors.""" dot = vdot(hat(v1), hat(v2)) n1, n2 = norm(v1), norm(v2) # locate for null vector(s) if np.ndim(dot) == 0 and (dot >= 1 or n1 == 0 or n2 == 0): return 0 if np.ndim(dot) > 0: # Remove invalid values and null vectors dot[(dot > 1) | (n1 == 0) | (n2 == 0)] = 1 return np.degrees(np.arccos(dot))
[docs]def dist(v1, v2): """Distance between two vectors.""" return np.sqrt(np.sum(np.power(np.subtract(v1, v2), 2), axis=-1))
[docs]def scalar_proj(v, n): r"""Scalar projection. .. code-block:: text s `n` o---|----> \ \ `v` x `v` · `n` s = --------- ||n|| Parameters ---------- v: list or numpy.ndarray Vector to project on the plane. n: list or numpy.ndarray Plane normal vector. Returns ------- float or numpy.ndarray Scalar resolute of v in the direction of n. """ return vdot(v, hat(n))
[docs]def scalar_rejection(v, n): r"""Scalar rejection. Orthogonal component u of the vector `v` in the plane of normal `n`. Also known as perpendicular dot product. .. code-block:: text `n` o--------> |\ `u` | \ `v` v x `v` · `n` `u` = `v` - --------- `n` ||n|| p² = ||u||² = ||v||² - [(`v` · `n`) / ||n||]² Parameters ---------- v: list or numpy.ndarray Vector to project on the plane. n: list or numpy.ndarray Plane normal vector. Returns ------- float or numpy.ndarray Scalar resolute of v orthogonal to the direction of n. """ return np.sqrt(norm(v)**2 - scalar_proj(v, n)**2)
[docs]def vector_rejection(v, n): r"""Vector rejection. Orthogonal vector `u` of the vector `v` in the plane of normal `n`. .. code-block:: text `n` o--------> |\ `u` | \ `v` v x `v` · `n` `u` = `v` - --------- `n` ||n|| Parameters ---------- v: list or numpy.ndarray Vector(s) to project on the plane. n: list or numpy.ndarray Plane normal vector. Returns ------- float or numpy.ndarray Scalar resolute of v orthogonal to the direction of n. """ proj = scalar_proj(v, n) if np.ndim(n) == 1: proj = np.transpose([proj]) else: proj = np.broadcast_to(proj[..., None], np.shape(n)) return np.subtract(v, proj * hat(n))
def ell_norm(xyz, radii): """Normal vector on a ellipsoid. Parameters ---------- xyz: numpy.ndarray Cartesian coordinates input point. radii: [float, float, float] Ellipsoid (a, b, c) radii. Returns ------- numpy.ndarray Normalized vector pointing away from the ellipsoid and normal to the ellipsoid at input point. """ m = np.min(radii) if m <= 0: raise ValueError('Radii must be positives.') # Scaled inverted squared radii inv_abc_2 = np.power(np.divide(m, radii), 2) # Normal vector to the ellipsoid v = np.multiply(xyz, inv_abc_2) return hat(v) def rot_axis(angle, axis): """Rotation matrix around an axis. Parameters ---------- angle: float or list Angle(s) of the rotation (degrees). axis: tuple Axis of rotation. Returns ------- numpy.ndarray Rotation matrix around the axis. """ ux, uy, uz = hat(axis) c, s = c, s = cs(angle) _c = 1 - c m = np.array([ [c + ux ** 2 * _c, ux * uy * _c - uz * s, ux * uz * _c + uy * s], [uy * ux * _c + uz * s, c + uy ** 2 * _c, uy * uz * _c - ux * s], [uz * ux * _c - uy * s, uz * uy * _c + ux * s, c + uz ** 2 * _c], ]) return m if np.ndim(m) < 3 else np.moveaxis(m, -1, 0) def boresight_rot_m(vec): """Rotation matrix to align the boresight to the z-axis.""" x, y, z = hat(vec) if x == y == z == 0: raise ValueError('The boresight can not be a null vector') w = np.sqrt(1 - z**2) r = norm([x, y]) c, s = (x / r, y / r) if r != 0 else (1, 0) return np.array([ [c * z, s * z, -w], [-s, c, 0], [c * w, s * w, z], ])