Source code for sphedron.transform

# License: Non-Commercial Use Only
#
# Permission is granted to use, copy, modify, and distribute this software
# for non-commercial purposes only, with attribution to the original author.
# Commercial use requires explicit permission.
#
# This software is provided "as is", without warranty of any kind.
"""Coordinate conversions between Cartesian, lat/lon, and theta/phi."""
from typing import List
from numpy.typing import NDArray
import numpy as np
from scipy.spatial.transform import Rotation
from .helpers import get_rotation_matrices as _get_rotations


[docs] def rotate_senders_by_receivers( receivers_xyz: NDArray, senders_xyz: NDArray, zero_latitude: bool = False, zero_longitude: bool = False, ): """ Apply rotation that zeroes out receivers' latitude and/or longitude to Cartesian senders' coordinates. Args: receivers_xyz: An NDArray representing the coordinates of the receivers in Cartesian format, shape (N,3) senders_xyz: An NDArray representing the coordinates of the senders in Cartesian format, shape (N,3) zero_latitude: A boolean flag indicating whether to zero out the latitude of the receivers. Defaults to False. zero_longitude: A boolean flag indicating whether to zero out the longitude of the receivers. Defaults to False. Returns: NDArray for shape (N,3) of the senders' rotated coordinates """ references_thetaphi = xyz_to_thetaphi(receivers_xyz) rotation_matrices = _get_rotations( references_thetaphi, zero_latitude=zero_latitude, zero_longitude=zero_longitude, ) # faster than expand_dims or matmul return np.einsum("nij, nj-> ni", rotation_matrices, senders_xyz)
[docs] def sender2receiver_edge_coords( sender_nodes: NDArray, receiver_nodes: NDArray, sender2receiver_edges: NDArray, zero_latitude: bool = True, zero_longitude: bool = True, ): """ Rotates the nodes so that the receivers have 0 latitude and longitude and computes the difference between the receivers and their senders coords Args: sender_nodes: An array of the nodes of the sender mesh, (N,3) receiver_nodes: An array of the nodes of receiver mesh, (M,3) sender2receiver_edges: An array representing edges from senders to receivers, (E, 2) zero_latitude: If True, adjusts the latitude of receivers to 0. zero_longitude: If True, adjusts the longitude of receivers to 0. Returns: An array of coordinates representing the differences between the receivers and their corresponding senders, shaped (E, 3) """ senders_wrt_receivers = rotate_senders_by_receivers( receiver_nodes[sender2receiver_edges[:, 1]], sender_nodes[sender2receiver_edges[:, 0]], zero_latitude=zero_latitude, zero_longitude=zero_longitude, ) receivers_wrt_receivers = rotate_senders_by_receivers( receiver_nodes[sender2receiver_edges[:, 1]], receiver_nodes[sender2receiver_edges[:, 1]], zero_latitude=zero_latitude, zero_longitude=zero_longitude, ) return senders_wrt_receivers - receivers_wrt_receivers
[docs] def rotate_nodes( nodes_xyz: NDArray, axis: str, angles: float | List[float], ) -> NDArray: """ Rotate the mesh nodes around a specified axis by a given angle. Args: nodes_xyz: Cartesian coordinates of the nodes, shape (N, 3) axis: axis around which to rotate the nodes, scipy's Euler rotation. angles: The angle (radian) by which to rotate the nodes. Returns: The rotated nodes of shape (num_nodes, 3). """ rotation = Rotation.from_euler(seq=axis, angles=angles).as_matrix() nodes_xyz = np.dot(nodes_xyz, rotation.T) return nodes_xyz
[docs] def xyz_to_thetaphi(xyz: NDArray) -> NDArray: """Convert Cartesian coordinates to spherical""" xyz = xyz / np.linalg.norm(xyz, axis=-1, keepdims=True) theta = np.arccos(xyz[:, 2]) phi = np.arctan2(xyz[:, 1], xyz[:, 0]) return np.c_[theta, phi]
[docs] def thetaphi_to_xyz(thetaphi: NDArray): """Convert spherical coordinates on the sphere (r=1) to Cartesian""" return np.c_[ np.cos(thetaphi[:, 1]) * np.sin(thetaphi[:, 0]), np.sin(thetaphi[:, 1]) * np.sin(thetaphi[:, 0]), np.cos(thetaphi[:, 0]), ]
[docs] def latlong_to_thetaphi(latlong: NDArray) -> NDArray: """ Convert latitude-longitude to spherical (theta, phi) where theta is the inclination (angle from positive z-axis) and phi the azimuth (z-axis rotation). """ return np.c_[np.deg2rad(90 - latlong[:, [0]]), np.deg2rad(latlong[:, [1]])]
[docs] def thetaphi_to_latlong(thetaphi: NDArray): """Convert spherical to latitude/longitude""" lats = 90 - np.rad2deg(thetaphi[:, 0]) longs = np.rad2deg(thetaphi[:, 1]) return np.c_[lats, longs]
[docs] def xyz_to_latlong(xyz: NDArray) -> NDArray: """Cartesian coordinates on the unit sphere to latitude,longitude""" return thetaphi_to_latlong(xyz_to_thetaphi(xyz))
[docs] def latlong_to_xyz(latlong: NDArray) -> NDArray: """latitude,longitude to Cartesian coordinates on the unit sphere""" return thetaphi_to_xyz(latlong_to_thetaphi(latlong))