Source code for gwcs.geometry

# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Models for general analytical geometry transformations.
"""

import numbers
import numpy as np
from astropy.modeling.core import Model
from astropy import units as u


__all__ = ['ToDirectionCosines', 'FromDirectionCosines',
           'SphericalToCartesian', 'CartesianToSpherical']


[docs] class ToDirectionCosines(Model): """ Transform a vector to direction cosines. """ _separable = False n_inputs = 3 n_outputs = 4 def __init__(self, **kwargs): super().__init__(**kwargs) self.inputs = ('x', 'y', 'z') self.outputs = ('cosa', 'cosb', 'cosc', 'length')
[docs] def evaluate(self, x, y, z): vabs = np.sqrt(1. + x**2 + y**2) cosa = x / vabs cosb = y / vabs cosc = 1. / vabs return cosa, cosb, cosc, vabs
def inverse(self): return FromDirectionCosines()
[docs] class FromDirectionCosines(Model): """ Transform directional cosines to vector. """ _separable = False n_inputs = 4 n_outputs = 3 def __init__(self, **kwargs): super().__init__(**kwargs) self.inputs = ('cosa', 'cosb', 'cosc', 'length') self.outputs = ('x', 'y', 'z')
[docs] def evaluate(self, cosa, cosb, cosc, length): return cosa * length, cosb * length, cosc * length
def inverse(self): return ToDirectionCosines()
[docs] class SphericalToCartesian(Model): """ Convert spherical coordinates on a unit sphere to cartesian coordinates. Spherical coordinates when not provided as ``Quantity`` are assumed to be in degrees with ``lon`` being the *longitude (or azimuthal) angle* ``[0, 360)`` (or ``[-180, 180)``) and angle ``lat`` is the *latitude* (or *elevation angle*) in range ``[-90, 90]``. """ _separable = False _input_units_allow_dimensionless = True n_inputs = 2 n_outputs = 3 def __init__(self, wrap_lon_at=360, **kwargs): """ Parameters ---------- wrap_lon_at : {360, 180}, optional An **integer number** that specifies the range of the longitude (azimuthal) angle. When ``wrap_lon_at`` is 180, the longitude angle will have a range of ``[-180, 180)`` and when ``wrap_lon_at`` is 360 (default), the longitude angle will have a range of ``[0, 360)``. """ super().__init__(**kwargs) self.inputs = ('lon', 'lat') self.outputs = ('x', 'y', 'z') self.wrap_lon_at = wrap_lon_at @property def wrap_lon_at(self): """ An **integer number** that specifies the range of the longitude (azimuthal) angle. Allowed values are 180 and 360. When ``wrap_lon_at`` is 180, the longitude angle will have a range of ``[-180, 180)`` and when ``wrap_lon_at`` is 360 (default), the longitude angle will have a range of ``[0, 360)``. """ return self._wrap_lon_at @wrap_lon_at.setter def wrap_lon_at(self, wrap_angle): if not (isinstance(wrap_angle, numbers.Integral) and wrap_angle in [180, 360]): raise ValueError("'wrap_lon_at' must be an integer number: 180 or 360") self._wrap_lon_at = wrap_angle
[docs] def evaluate(self, lon, lat): if isinstance(lon, u.Quantity) != isinstance(lat, u.Quantity): raise TypeError("All arguments must be of the same type " "(i.e., quantity or not).") lon = np.deg2rad(lon) lat = np.deg2rad(lat) cs = np.cos(lat) x = cs * np.cos(lon) y = cs * np.sin(lon) z = np.sin(lat) return x, y, z
def inverse(self): return CartesianToSpherical(wrap_lon_at=self._wrap_lon_at) @property def input_units(self): return {'lon': u.deg, 'lat': u.deg}
[docs] class CartesianToSpherical(Model): """ Convert cartesian coordinates to spherical coordinates on a unit sphere. Output spherical coordinates are in degrees. When input cartesian coordinates are quantities (``Quantity`` objects), output angles will also be quantities in degrees. Angle ``lon`` is the *longitude* (or *azimuthal angle*) in range ``[0, 360)`` (or ``[-180, 180)``) and angle ``lat`` is the *latitude* (or *elevation angle*) in the range ``[-90, 90]``. """ _separable = False _input_units_allow_dimensionless = True n_inputs = 3 n_outputs = 2 def __init__(self, wrap_lon_at=360, **kwargs): """ Parameters ---------- wrap_lon_at : {360, 180}, optional An **integer number** that specifies the range of the longitude (azimuthal) angle. When ``wrap_lon_at`` is 180, the longitude angle will have a range of ``[-180, 180)`` and when ``wrap_lon_at`` is 360 (default), the longitude angle will have a range of ``[0, 360)``. """ super().__init__(**kwargs) self.inputs = ('x', 'y', 'z') self.outputs = ('lon', 'lat') self.wrap_lon_at = wrap_lon_at @property def wrap_lon_at(self): """ An **integer number** that specifies the range of the longitude (azimuthal) angle. Allowed values are 180 and 360. When ``wrap_lon_at`` is 180, the longitude angle will have a range of ``[-180, 180)`` and when ``wrap_lon_at`` is 360 (default), the longitude angle will have a range of ``[0, 360)``. """ return self._wrap_lon_at @wrap_lon_at.setter def wrap_lon_at(self, wrap_angle): if not (isinstance(wrap_angle, numbers.Integral) and wrap_angle in [180, 360]): raise ValueError("'wrap_lon_at' must be an integer number: 180 or 360") self._wrap_lon_at = wrap_angle
[docs] def evaluate(self, x, y, z): nquant = [isinstance(i, u.Quantity) for i in (x, y, z)].count(True) if nquant in [1, 2]: raise TypeError("All arguments must be of the same type " "(i.e., quantity or not).") h = np.hypot(x, y) lat = np.rad2deg(np.arctan2(z, h)) lon = np.rad2deg(np.arctan2(y, x)) lon[h == 0] *= 0 if self._wrap_lon_at != 180: lon = np.mod(lon, 360.0 * u.deg if nquant else 360.0, where=np.isfinite(lon), out=lon) return lon, lat
def inverse(self): return SphericalToCartesian(wrap_lon_at=self._wrap_lon_at)