pygeodesy 25.11.5__py2.py3-none-any.whl → 25.12.12__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygeodesy/__init__.py +25 -12
- pygeodesy/__main__.py +1 -1
- pygeodesy/albers.py +1 -1
- pygeodesy/angles.py +960 -0
- pygeodesy/auxilats/_CX_4.py +1 -1
- pygeodesy/auxilats/_CX_6.py +1 -1
- pygeodesy/auxilats/_CX_8.py +1 -1
- pygeodesy/auxilats/_CX_Rs.py +1 -1
- pygeodesy/auxilats/__init__.py +2 -2
- pygeodesy/auxilats/__main__.py +1 -1
- pygeodesy/auxilats/auxAngle.py +7 -8
- pygeodesy/auxilats/auxDLat.py +1 -1
- pygeodesy/auxilats/auxDST.py +1 -1
- pygeodesy/auxilats/auxLat.py +1 -1
- pygeodesy/auxilats/auxily.py +1 -1
- pygeodesy/azimuthal.py +6 -5
- pygeodesy/basics.py +14 -10
- pygeodesy/booleans.py +1 -1
- pygeodesy/cartesianBase.py +7 -7
- pygeodesy/clipy.py +1 -1
- pygeodesy/constants.py +27 -24
- pygeodesy/css.py +1 -1
- pygeodesy/datums.py +1 -1
- pygeodesy/deprecated/__init__.py +1 -1
- pygeodesy/deprecated/bases.py +1 -1
- pygeodesy/deprecated/classes.py +14 -7
- pygeodesy/deprecated/consterns.py +1 -1
- pygeodesy/deprecated/datum.py +1 -1
- pygeodesy/deprecated/functions.py +1 -1
- pygeodesy/deprecated/nvector.py +1 -1
- pygeodesy/deprecated/rhumbBase.py +1 -1
- pygeodesy/deprecated/rhumbaux.py +1 -1
- pygeodesy/deprecated/rhumbsolve.py +1 -1
- pygeodesy/deprecated/rhumbx.py +1 -1
- pygeodesy/dms.py +1 -1
- pygeodesy/ecef.py +1 -1
- pygeodesy/ecefLocals.py +1 -1
- pygeodesy/elevations.py +1 -1
- pygeodesy/ellipsoidalBase.py +1 -1
- pygeodesy/ellipsoidalBaseDI.py +1 -1
- pygeodesy/ellipsoidalExact.py +1 -1
- pygeodesy/ellipsoidalGeodSolve.py +1 -1
- pygeodesy/ellipsoidalKarney.py +1 -1
- pygeodesy/ellipsoidalNvector.py +1 -1
- pygeodesy/ellipsoidalVincenty.py +1 -1
- pygeodesy/ellipsoids.py +7 -6
- pygeodesy/elliptic.py +1 -1
- pygeodesy/epsg.py +1 -1
- pygeodesy/errors.py +8 -4
- pygeodesy/etm.py +1 -1
- pygeodesy/fmath.py +15 -8
- pygeodesy/formy.py +107 -5
- pygeodesy/frechet.py +1 -1
- pygeodesy/fstats.py +1 -1
- pygeodesy/fsums.py +1 -1
- pygeodesy/gars.py +1 -1
- pygeodesy/geod3solve.py +488 -0
- pygeodesy/geodesici.py +4 -4
- pygeodesy/geodesicw.py +1 -1
- pygeodesy/geodesicx/_C4_24.py +1 -1
- pygeodesy/geodesicx/_C4_27.py +1 -1
- pygeodesy/geodesicx/_C4_30.py +1 -1
- pygeodesy/geodesicx/__init__.py +1 -1
- pygeodesy/geodesicx/__main__.py +1 -1
- pygeodesy/geodesicx/gx.py +1 -1
- pygeodesy/geodesicx/gxarea.py +1 -1
- pygeodesy/geodesicx/gxbases.py +1 -1
- pygeodesy/geodesicx/gxline.py +1 -1
- pygeodesy/geodsolve.py +70 -102
- pygeodesy/geohash.py +1 -1
- pygeodesy/geoids.py +1 -1
- pygeodesy/hausdorff.py +1 -1
- pygeodesy/heights.py +1 -1
- pygeodesy/internals.py +1 -1
- pygeodesy/interns.py +3 -3
- pygeodesy/iters.py +1 -1
- pygeodesy/karney.py +132 -116
- pygeodesy/ktm.py +1 -1
- pygeodesy/latlonBase.py +1 -1
- pygeodesy/lazily.py +23 -12
- pygeodesy/lcc.py +1 -1
- pygeodesy/ltp.py +1 -1
- pygeodesy/ltpTuples.py +1 -1
- pygeodesy/mgrs.py +3 -3
- pygeodesy/named.py +14 -9
- pygeodesy/namedTuples.py +1 -1
- pygeodesy/nvectorBase.py +1 -1
- pygeodesy/osgr.py +1 -1
- pygeodesy/points.py +1 -1
- pygeodesy/props.py +1 -1
- pygeodesy/resections.py +1 -1
- pygeodesy/rhumb/__init__.py +8 -6
- pygeodesy/rhumb/aux_.py +1 -1
- pygeodesy/rhumb/bases.py +1 -1
- pygeodesy/rhumb/ekx.py +1 -1
- pygeodesy/rhumb/solve.py +91 -84
- pygeodesy/simplify.py +1 -1
- pygeodesy/solveBase.py +72 -49
- pygeodesy/sphericalBase.py +1 -1
- pygeodesy/sphericalNvector.py +1 -1
- pygeodesy/sphericalTrigonometry.py +1 -1
- pygeodesy/streprs.py +6 -4
- pygeodesy/trf.py +1 -1
- pygeodesy/triaxials/__init__.py +70 -0
- pygeodesy/triaxials/bases.py +935 -0
- pygeodesy/triaxials/conformal3.py +617 -0
- pygeodesy/triaxials/triaxial3.py +969 -0
- pygeodesy/{triaxials.py → triaxials/triaxial5.py} +353 -781
- pygeodesy/units.py +1 -1
- pygeodesy/unitsBase.py +1 -1
- pygeodesy/ups.py +2 -3
- pygeodesy/utily.py +17 -14
- pygeodesy/utm.py +1 -1
- pygeodesy/utmups.py +1 -1
- pygeodesy/utmupsBase.py +1 -1
- pygeodesy/vector2d.py +1 -1
- pygeodesy/vector3d.py +1 -1
- pygeodesy/vector3dBase.py +1 -1
- pygeodesy/webmercator.py +1 -1
- pygeodesy/wgrs.py +1 -1
- {pygeodesy-25.11.5.dist-info → pygeodesy-25.12.12.dist-info}/METADATA +12 -12
- pygeodesy-25.12.12.dist-info/RECORD +125 -0
- pygeodesy-25.11.5.dist-info/RECORD +0 -119
- {pygeodesy-25.11.5.dist-info → pygeodesy-25.12.12.dist-info}/WHEEL +0 -0
- {pygeodesy-25.11.5.dist-info → pygeodesy-25.12.12.dist-info}/top_level.txt +0 -0
pygeodesy/angles.py
ADDED
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Classes L{Ang}, L{Deg}, L{Rad} and L{Lambertian} accurately representing an angle
|
|
5
|
+
as a 3-tuple C{(sine, cosine, turns)}, with C{turns} the number of full turns.
|
|
6
|
+
|
|
7
|
+
Transcoded to pure Python from I{Karney}'s GeographicLib 2.7 C++ class U{AngleT
|
|
8
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1AngleT.html>}.
|
|
9
|
+
|
|
10
|
+
Copyright (C) U{Charles Karney <mailto:Karney@Alum.MIT.edu>} (2024-2025) and licensed
|
|
11
|
+
under the MIT/X11 License. For more information, see the U{GeographicLib 2.7
|
|
12
|
+
<https://GeographicLib.SourceForge.io/>} documentation.
|
|
13
|
+
'''
|
|
14
|
+
# make sure int/int division yields float quotient, see .basics
|
|
15
|
+
from __future__ import division as _; del _ # noqa: E702 ;
|
|
16
|
+
|
|
17
|
+
from pygeodesy.basics import _copysign, map1, signBit, _signOf
|
|
18
|
+
from pygeodesy.constants import EPS, EPS0, NAN, PI2, _0_0, _N_0_0, \
|
|
19
|
+
_0_25, _1_0, _N_1_0, _4_0, _360_0, \
|
|
20
|
+
_copysign_0_0, _copysign_1_0, \
|
|
21
|
+
_flipsign, float_, _isfinite, \
|
|
22
|
+
_over, _pos_self, remainder
|
|
23
|
+
from pygeodesy.errors import _xkwds, _xkwds_get, _xkwds_pop2
|
|
24
|
+
from pygeodesy.fmath import hypot, _ALL_LAZY, _MODS
|
|
25
|
+
# from pygeodesy.interns import _COMMASPACE_ # from .streprs
|
|
26
|
+
# from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .fmath
|
|
27
|
+
from pygeodesy.named import _Named, _NamedTuple, _Pass
|
|
28
|
+
from pygeodesy.props import Property_RO, property_doc_, property_RO, \
|
|
29
|
+
_allPropertiesOf_n, _update_all
|
|
30
|
+
from pygeodesy.streprs import Fmt, fstr, unstr, _COMMASPACE_
|
|
31
|
+
from pygeodesy.units import Degrees, _isDegrees, _isRadians, Radians
|
|
32
|
+
from pygeodesy.utily import atan2, atan2d, sincos2, sincos2d, SinCos2
|
|
33
|
+
|
|
34
|
+
from math import asinh, ceil as _ceil, fabs, floor as _floor, \
|
|
35
|
+
isinf, isnan, sinh
|
|
36
|
+
|
|
37
|
+
__all__ = _ALL_LAZY.angles
|
|
38
|
+
__version__ = '25.12.02'
|
|
39
|
+
|
|
40
|
+
_EPS03 = EPS / (1 << 20)
|
|
41
|
+
# _HD = _180_0
|
|
42
|
+
# _QD = _90_0
|
|
43
|
+
# _TD = _360_0
|
|
44
|
+
# _DM = _SM = _60_0
|
|
45
|
+
# _DS = _3600_0
|
|
46
|
+
_ZRND = _1_0 / 1024
|
|
47
|
+
|
|
48
|
+
_CARDINAL2 = {-2: (_N_0_0, _N_1_0),
|
|
49
|
+
-1: (_N_1_0, _0_0),
|
|
50
|
+
1: ( _1_0, _0_0),
|
|
51
|
+
2: ( _0_0, _N_1_0)}.get
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _fint(f):
|
|
55
|
+
# float of C{int(f)} preserving signed C{0}.
|
|
56
|
+
i = int(f)
|
|
57
|
+
return float_(i) if i else _copysign_0_0(f)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _ncardinal(s, c, n):
|
|
61
|
+
if n:
|
|
62
|
+
n *= _4_0
|
|
63
|
+
i = (1 if (-c) < fabs(s) else 2) if signBit(c) else \
|
|
64
|
+
(1 if c < fabs(s) else 0)
|
|
65
|
+
if i:
|
|
66
|
+
n += _copysign(i, s)
|
|
67
|
+
return n
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _normalize2(s, c):
|
|
71
|
+
h = hypot(s, c)
|
|
72
|
+
if _isfinite(h):
|
|
73
|
+
sc = ((s / h), (c / h)) if h else (
|
|
74
|
+
# If y is +/-0 and x = -0, +/-pi is returned,
|
|
75
|
+
# or y is +/-0 and x = +0, +/-0 is returned,
|
|
76
|
+
# so, retain the sign of s = +/-0
|
|
77
|
+
_orthogonal2(False, s, c))
|
|
78
|
+
elif isnan(h) or (isinf(s) and isinf(c)):
|
|
79
|
+
sc = NAN, NAN
|
|
80
|
+
else:
|
|
81
|
+
sc = _orthogonal2(isinf(s), s, c)
|
|
82
|
+
return sc
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _other(x, unit=Radians, **unused):
|
|
86
|
+
# get C{x} as C{Ang} from C{Degrees}, C{Radians} or C{Lambertian}
|
|
87
|
+
return Ang.fromLambertian(x) if unit is Lambertian else (
|
|
88
|
+
Ang.fromRadians(x) if _isRadians(x, iscalar=unit is Radians) else (
|
|
89
|
+
Ang.fromDegrees(x) if _isDegrees(x, iscalar=unit is Degrees) else
|
|
90
|
+
_raiseError(unit, x))) # PYCHOK indent
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _orthogonal2(pred, s, c):
|
|
94
|
+
return (_copysign_1_0(s), _copysign_0_0(c)) if pred else \
|
|
95
|
+
(_copysign_0_0(s), _copysign_1_0(c))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _raiseError(unit, arg, **kwds):
|
|
99
|
+
raise TypeError(unstr(unit, arg, **kwds))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _rnd(x):
|
|
103
|
+
w = _ZRND - fabs(x)
|
|
104
|
+
if w > 0:
|
|
105
|
+
x = _copysign(_ZRND - w, x)
|
|
106
|
+
return x
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _scnu4(s, c, n, unit=Radians, **unused): # unit=Ang._unit
|
|
110
|
+
s, c, n = map1(float, s, c, n)
|
|
111
|
+
return _normalize2(s, c) + (n, unit)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Ang(_Named):
|
|
115
|
+
'''An accurate representation of angles, as 3-tuple C{(s, c, n)}.
|
|
116
|
+
|
|
117
|
+
This class represents an angle via its sine C{s}, cosine C{c} and
|
|
118
|
+
the number of full turns C{n}. The angle is then C{atan2(s, c) +
|
|
119
|
+
n * PI2}. This representation offers several advantages:
|
|
120
|
+
|
|
121
|
+
- cardinal directions (multiples of 90 degrees) are exactly represented
|
|
122
|
+
(a benefit shared by representing angles as degrees)
|
|
123
|
+
|
|
124
|
+
- angles very close to any cardinal direction are accurately represented
|
|
125
|
+
|
|
126
|
+
- there's no loss of precision with large angles (outside the "normal"
|
|
127
|
+
range [-180, +180])
|
|
128
|
+
|
|
129
|
+
- various operations, such as adding a multiple of 90 degrees to an
|
|
130
|
+
angle are performed exactly.
|
|
131
|
+
|
|
132
|
+
@note: B{C{n}} is a C{float}, this allows it to be NAN, INF or NINF.
|
|
133
|
+
'''
|
|
134
|
+
_unit = Radians # see _scnu4
|
|
135
|
+
|
|
136
|
+
def __init__(self, s_ang=0, c=None, n=0, normal=True, **unit_name):
|
|
137
|
+
'''New L{Ang}.
|
|
138
|
+
|
|
139
|
+
@kwarg s_ang: A previous L{Ang}, C{Degrees}, C{Radians} if C{B{c}
|
|
140
|
+
is None}, otherwise the sine component (C{float}).
|
|
141
|
+
@kwarg c: The cosine component (C{float}) iff C{not None}.
|
|
142
|
+
@kwarg n: The number of L{PI2} turns (C{float}).
|
|
143
|
+
@kwarg normal: If C{True}, B{C{s}} and B{C{c}} are normalized, i.e.
|
|
144
|
+
on the unit circle (C{boo}).
|
|
145
|
+
@kwarg unit_name: Type C{B{unit}=}L{Radians} or L{Degrees} of scalar
|
|
146
|
+
scalar values (L{Degrees} or L{Radians}).
|
|
147
|
+
|
|
148
|
+
@note: Either B{C{s}} or B{C{c}} can be INF or NINF, but not both.
|
|
149
|
+
|
|
150
|
+
@note: By default, the point B{C{(s, c)}} is scaled to lie on the
|
|
151
|
+
unit circle.
|
|
152
|
+
'''
|
|
153
|
+
s, c, n, u = s_ang.scnu4 if isAng(s_ang) else (
|
|
154
|
+
_other(s_ang, **unit_name).scnu4 if c is None else
|
|
155
|
+
_scnu4(s_ang, c, n, **unit_name))
|
|
156
|
+
if unit_name:
|
|
157
|
+
u, name = _xkwds_pop2(unit_name, unit=u)
|
|
158
|
+
if name:
|
|
159
|
+
self.name = name
|
|
160
|
+
self._n = _fint(n)
|
|
161
|
+
self._s, self._c = (s, c) if normal else _normalize2(s, c)
|
|
162
|
+
self.unit = u
|
|
163
|
+
|
|
164
|
+
def __abs__(self):
|
|
165
|
+
s, _ = self._float2()
|
|
166
|
+
return self._float1(fabs(s))
|
|
167
|
+
|
|
168
|
+
def __add__(self, other):
|
|
169
|
+
return self.copy().__iadd__(other)
|
|
170
|
+
|
|
171
|
+
def __bool__(self): # PYCHOK Python 3+
|
|
172
|
+
s, c, n = self.scn3
|
|
173
|
+
return bool(s or c or n)
|
|
174
|
+
|
|
175
|
+
# def __call__(self, *args, **kwds): # PYCHOK no cover
|
|
176
|
+
# return self._NotImplemented(*args, **kwds)
|
|
177
|
+
|
|
178
|
+
def __ceil__(self): # PYCHOK not special in Python 2-
|
|
179
|
+
s, _ = self._float2()
|
|
180
|
+
return self._float1(_ceil(s))
|
|
181
|
+
|
|
182
|
+
def __cmp__(self, other): # PYCHOK no cover
|
|
183
|
+
s, r = self._float2(other)
|
|
184
|
+
return _signOf(s, r) # -1, 0, +1
|
|
185
|
+
|
|
186
|
+
def __divmod__(self, other):
|
|
187
|
+
s, r = self._float2(other)
|
|
188
|
+
q, r = divmod(s, r)
|
|
189
|
+
return q, self._float1(r)
|
|
190
|
+
|
|
191
|
+
def __eq__(self, other):
|
|
192
|
+
s, r = self._float2(other)
|
|
193
|
+
return fabs(s - r) < EPS0
|
|
194
|
+
|
|
195
|
+
def __float__(self):
|
|
196
|
+
u = self.unit
|
|
197
|
+
return self.radians if u is Radians else (
|
|
198
|
+
self.degrees if u is Degrees else (
|
|
199
|
+
self.lambertian if u is Lambertian else
|
|
200
|
+
_raiseError(float, u))) # PYCHOK indent
|
|
201
|
+
|
|
202
|
+
def __floor__(self): # PYCHOK not special in Python 2-
|
|
203
|
+
s, _ = self._float2()
|
|
204
|
+
return self._float1(_floor(s))
|
|
205
|
+
|
|
206
|
+
def __floordiv__(self, other):
|
|
207
|
+
return self.copy().__ifloordiv__(other)
|
|
208
|
+
|
|
209
|
+
# def __format__(self, *other): # PYCHOK no cover
|
|
210
|
+
# return self._NotImplemented(self, *other)
|
|
211
|
+
|
|
212
|
+
def __ge__(self, other):
|
|
213
|
+
s, r = self._float2(other)
|
|
214
|
+
return s >= r
|
|
215
|
+
|
|
216
|
+
def __gt__(self, other):
|
|
217
|
+
s, r = self._float2(other)
|
|
218
|
+
return s > r
|
|
219
|
+
|
|
220
|
+
def __hash__(self): # PYCHOK no cover
|
|
221
|
+
# @see: U{Notes for type implementors<https://docs.Python.org/
|
|
222
|
+
# 3/library/numbers.html#numbers.Rational>}
|
|
223
|
+
return hash(self.scn3) # tuple.__hash__()
|
|
224
|
+
|
|
225
|
+
def __iadd__(self, other):
|
|
226
|
+
p = self._other(other)
|
|
227
|
+
q = p.ncardinal + self.ncardinal
|
|
228
|
+
s, c, n = self.scn3
|
|
229
|
+
s, c = _normalize2(s * p.c + c * p.s,
|
|
230
|
+
c * p.c - s * p.s)
|
|
231
|
+
q -= _ncardinal(s, c, n)
|
|
232
|
+
n = _fint(q * _0_25) + p.n
|
|
233
|
+
if n:
|
|
234
|
+
self._n += n
|
|
235
|
+
self._s = s
|
|
236
|
+
self._c = c
|
|
237
|
+
_update_all(self)
|
|
238
|
+
return self._update(s, c)
|
|
239
|
+
|
|
240
|
+
def __ifloordiv__(self, other):
|
|
241
|
+
s, r = self._float2(other)
|
|
242
|
+
return self._ifloat(s // r)
|
|
243
|
+
|
|
244
|
+
def __imatmul__(self, other): # PYCHOK no cover
|
|
245
|
+
return self._notImplemented()
|
|
246
|
+
|
|
247
|
+
def __imod__(self, other):
|
|
248
|
+
s, r = self._float2(other)
|
|
249
|
+
return self._ifloat(s % r)
|
|
250
|
+
|
|
251
|
+
def __imul__(self, other):
|
|
252
|
+
s, r = self._float2(other)
|
|
253
|
+
return self._ifloat(s * r)
|
|
254
|
+
|
|
255
|
+
def __int__(self):
|
|
256
|
+
s, _ = self._float2(0)
|
|
257
|
+
return int(s)
|
|
258
|
+
|
|
259
|
+
def __invert__(self): # PYCHOK no cover
|
|
260
|
+
# Luciano Ramalho, "Fluent Python", O'Reilly, 2nd Ed, 2022 p. 567
|
|
261
|
+
return self._notImplemented()
|
|
262
|
+
|
|
263
|
+
def __ipow__(self, other, *mod): # PYCHOK 2 vs 3 args
|
|
264
|
+
s, r = self._float2(other)
|
|
265
|
+
return self._ifloat(pow(s, r, *mod))
|
|
266
|
+
|
|
267
|
+
def __isub__(self, other):
|
|
268
|
+
return self.__iadd__(-other)
|
|
269
|
+
|
|
270
|
+
# def __iter__(self):
|
|
271
|
+
# '''
|
|
272
|
+
# return self._NotImplemented()
|
|
273
|
+
|
|
274
|
+
def __itruediv__(self, other):
|
|
275
|
+
s, r = self._float2(other)
|
|
276
|
+
return self._ifloat(s / r)
|
|
277
|
+
|
|
278
|
+
def __le__(self, other):
|
|
279
|
+
s, r = self._float2(other)
|
|
280
|
+
return s <= r
|
|
281
|
+
|
|
282
|
+
def __lt__(self, other):
|
|
283
|
+
s, r = self._float2(other)
|
|
284
|
+
return s < r
|
|
285
|
+
|
|
286
|
+
def __matmul__(self, other): # PYCHOK no cover
|
|
287
|
+
return self._notImplemented(other)
|
|
288
|
+
|
|
289
|
+
def __mod__(self, other):
|
|
290
|
+
s, r = self._float2(other)
|
|
291
|
+
return self._float1(s % r)
|
|
292
|
+
|
|
293
|
+
def __mul__(self, other):
|
|
294
|
+
return self.copy().__imul__(other)
|
|
295
|
+
|
|
296
|
+
def __ne__(self, other):
|
|
297
|
+
return not self.__eq__(other)
|
|
298
|
+
|
|
299
|
+
def __neg__(self):
|
|
300
|
+
s, c, n = self.scn3
|
|
301
|
+
s, n = _flipsign(s), _flipsign(n)
|
|
302
|
+
return self._Ang(s, c, n) # normal=True
|
|
303
|
+
|
|
304
|
+
def __pos__(self):
|
|
305
|
+
return self if _pos_self else self.copy()
|
|
306
|
+
|
|
307
|
+
def __pow__(self, other, *mod): # PYCHOK 2 vs 3 args
|
|
308
|
+
return self.copy().__ipow__(other, *mod)
|
|
309
|
+
|
|
310
|
+
def __radd__(self, other):
|
|
311
|
+
return self._other(other) + self
|
|
312
|
+
|
|
313
|
+
def __rdivmod__(self, other):
|
|
314
|
+
return divmod(self._other(other), self)
|
|
315
|
+
|
|
316
|
+
def __repr__(self):
|
|
317
|
+
return self.toRepr()
|
|
318
|
+
|
|
319
|
+
def __rfloordiv__(self, other):
|
|
320
|
+
return self._other(other) // self
|
|
321
|
+
|
|
322
|
+
def __rmatmul__(self, other): # PYCHOK no cover
|
|
323
|
+
return self._notImplemented(self, other)
|
|
324
|
+
|
|
325
|
+
def __rmod__(self, other):
|
|
326
|
+
return self._other(other) % self
|
|
327
|
+
|
|
328
|
+
def __rmul__(self, other):
|
|
329
|
+
return self._other(other) * self
|
|
330
|
+
|
|
331
|
+
def __round__(self, *ndigits): # PYCHOK Python 3+
|
|
332
|
+
return self.round(*ndigits)
|
|
333
|
+
|
|
334
|
+
def __rpow__(self, other, *mod):
|
|
335
|
+
return pow(self._other(other), self, *mod)
|
|
336
|
+
|
|
337
|
+
def __rsub__(self, other):
|
|
338
|
+
return self._other(other) - self
|
|
339
|
+
|
|
340
|
+
def __rtruediv__(self, other):
|
|
341
|
+
return self._other(other) / self
|
|
342
|
+
|
|
343
|
+
def __str__(self):
|
|
344
|
+
return self.toStr(0) # ignore turns
|
|
345
|
+
|
|
346
|
+
def __sub__(self, other):
|
|
347
|
+
return self.copy().__isub__(other)
|
|
348
|
+
|
|
349
|
+
def __truediv__(self, other):
|
|
350
|
+
return self.copy().__itruediv__(other)
|
|
351
|
+
|
|
352
|
+
__trunc__ = __int__
|
|
353
|
+
|
|
354
|
+
if _MODS.sys_version_info2 < (3, 0): # PYCHOK no cover
|
|
355
|
+
# <https://docs.Python.org/2/library/operator.html#mapping-operators-to-functions>
|
|
356
|
+
__div__ = __truediv__
|
|
357
|
+
__idiv__ = __itruediv__
|
|
358
|
+
__long__ = __int__
|
|
359
|
+
__nonzero__ = __bool__
|
|
360
|
+
__rdiv__ = __rtruediv__
|
|
361
|
+
|
|
362
|
+
def _Ang(self, s, *cn, **normal_unit_name):
|
|
363
|
+
# return an C{Ang} like C{self}
|
|
364
|
+
return Ang(s, *cn, **self._kwds(normal_unit_name))
|
|
365
|
+
|
|
366
|
+
def base(self, *center):
|
|
367
|
+
'''Return this C{Angle}'s base, optionally centered.
|
|
368
|
+
'''
|
|
369
|
+
r = self.copy()
|
|
370
|
+
if center:
|
|
371
|
+
c = self._other(center[0])
|
|
372
|
+
b = self - c
|
|
373
|
+
b = b.base()
|
|
374
|
+
b += c
|
|
375
|
+
r.n0 = b.n0
|
|
376
|
+
else:
|
|
377
|
+
r.n = 0
|
|
378
|
+
return r
|
|
379
|
+
|
|
380
|
+
@property_RO
|
|
381
|
+
def c(self):
|
|
382
|
+
'''Get the cosine of this C{Angle} (C{float}).
|
|
383
|
+
'''
|
|
384
|
+
return self._c
|
|
385
|
+
|
|
386
|
+
@staticmethod
|
|
387
|
+
def cardinal(q=0, **unit_name):
|
|
388
|
+
'''A cardinal direction.
|
|
389
|
+
|
|
390
|
+
@kwarg q: The number of I{quarter} turns (C{scalar}).
|
|
391
|
+
|
|
392
|
+
@return: An C{Ang} equivalent to B{C{q}} quarter turns.
|
|
393
|
+
|
|
394
|
+
@note: B{C{q}} is truncated to an integer and signed
|
|
395
|
+
C{0} is distinguished. C{Ang.NAN} is returned
|
|
396
|
+
if B{C{q}} is not finite.
|
|
397
|
+
'''
|
|
398
|
+
if _isfinite(q):
|
|
399
|
+
if q:
|
|
400
|
+
q = _fint(q)
|
|
401
|
+
i = int(remainder(q, _4_0)) # i is in [-2, 2]
|
|
402
|
+
n = _fint((q - i) * _0_25)
|
|
403
|
+
s, c = _CARDINAL2(i, ((_0_0 if q else q), _1_0))
|
|
404
|
+
t = s is not q
|
|
405
|
+
else:
|
|
406
|
+
s, c, n, t = _copysign_0_0(q), 1, 0, True
|
|
407
|
+
r = Ang(s, c, n, normal=t, **unit_name)
|
|
408
|
+
else:
|
|
409
|
+
r = Ang.NAN(**unit_name)
|
|
410
|
+
return r
|
|
411
|
+
|
|
412
|
+
def copy(self, **unit_name): # PYCHOK signature
|
|
413
|
+
'''Return a copy of this C{Ang}.
|
|
414
|
+
'''
|
|
415
|
+
return self._Ang(self, **self._kwds(unit_name))
|
|
416
|
+
|
|
417
|
+
@Property_RO
|
|
418
|
+
def degrees(self):
|
|
419
|
+
'''Get this C{Ang} in C{degrees}.
|
|
420
|
+
'''
|
|
421
|
+
d = self.degrees0
|
|
422
|
+
if self.n:
|
|
423
|
+
d += self.n * _360_0
|
|
424
|
+
return d # XXX Degrees(d, self.name)
|
|
425
|
+
|
|
426
|
+
@Property_RO
|
|
427
|
+
def degrees0(self):
|
|
428
|
+
'''Get this C{Ang} in C{degrees} ignoring the turns.
|
|
429
|
+
'''
|
|
430
|
+
return atan2d(*self.sc2) # XXX Degrees(d, self.name)
|
|
431
|
+
|
|
432
|
+
divmod = __divmod__
|
|
433
|
+
|
|
434
|
+
@staticmethod
|
|
435
|
+
def EPS0(**unit_name):
|
|
436
|
+
'''Get a tiny C{Ang}.
|
|
437
|
+
|
|
438
|
+
@note: This allows angles extremely close to the cardinal
|
|
439
|
+
directions to be generated. The C{.round} method
|
|
440
|
+
will flush this angle to C{0}.
|
|
441
|
+
'''
|
|
442
|
+
return Ang(_EPS03, 1, **unit_name)
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def _flip(bet, omg, alp=None):
|
|
446
|
+
'''(INTERNAL) Reflect C{bet}, C{omg} and C{alp} inplace.
|
|
447
|
+
''' # Ellipsoid3.Flip
|
|
448
|
+
bet.reflect(flipc=True)
|
|
449
|
+
omg.reflect(flips=True)
|
|
450
|
+
if alp:
|
|
451
|
+
alp.reflect(flips=True, flipc=True)
|
|
452
|
+
|
|
453
|
+
def flipsign(self, mul=-1, **name):
|
|
454
|
+
'''Copy this C{Ang} with sign flipped.
|
|
455
|
+
'''
|
|
456
|
+
r = (-self) if signBit(mul) else self
|
|
457
|
+
return self._Ang(r, **name) if name else r
|
|
458
|
+
|
|
459
|
+
def _float1(self, f, **name):
|
|
460
|
+
# return C{f} as C{Ang} in this C{unit}
|
|
461
|
+
return _Ang_from[self.unit](f, **name)
|
|
462
|
+
|
|
463
|
+
def _float2(self, other=None):
|
|
464
|
+
# get self and C{other} as floats
|
|
465
|
+
r = other if other is None or isinstance(other, int) else \
|
|
466
|
+
float(_Ang_from[self.unit](other))
|
|
467
|
+
return float(self), r
|
|
468
|
+
|
|
469
|
+
def _ifloat(self, f): # PYCHOK expected
|
|
470
|
+
# set self to C{f} degrees or radians
|
|
471
|
+
scn = self._float1(f).scn3
|
|
472
|
+
self._s, self._c, self._n = scn
|
|
473
|
+
return self._update()
|
|
474
|
+
|
|
475
|
+
@staticmethod
|
|
476
|
+
def fromDegrees(deg, **unit_name):
|
|
477
|
+
'''Get an C{Ang} from degrees.
|
|
478
|
+
'''
|
|
479
|
+
if isAng(deg):
|
|
480
|
+
s, c, n = deg.scn3
|
|
481
|
+
d = deg.degrees0
|
|
482
|
+
elif _isDegrees(deg, iscalar=True):
|
|
483
|
+
s, c = sincos2d(deg)
|
|
484
|
+
d = atan2d(s, c)
|
|
485
|
+
n = round((deg - d) / _360_0)
|
|
486
|
+
else:
|
|
487
|
+
_raiseError(Ang.fromDegrees, deg, **unit_name)
|
|
488
|
+
a = Ang(s, c, n, **_xkwds(unit_name, unit=Degrees))
|
|
489
|
+
a.__dict__.update(degrees0=d) # Property_RO
|
|
490
|
+
return a
|
|
491
|
+
|
|
492
|
+
@staticmethod
|
|
493
|
+
def fromLambertian(psi, **unit_name):
|
|
494
|
+
'''Get an C{Ang} from C{lamberterian} radians.
|
|
495
|
+
'''
|
|
496
|
+
s = psi.lambertian if isAng(psi) else sinh(psi)
|
|
497
|
+
return Ang(s, 1, normal=False, **_xkwds(unit_name, unit=Lambertian))
|
|
498
|
+
|
|
499
|
+
@staticmethod
|
|
500
|
+
def fromRadians(rad, **unit_name):
|
|
501
|
+
'''Get an C{Ang} from radians.
|
|
502
|
+
'''
|
|
503
|
+
if isAng(rad):
|
|
504
|
+
s, c, n = rad.scn3
|
|
505
|
+
r = rad.radians0
|
|
506
|
+
elif _isRadians(rad, iscalar=True):
|
|
507
|
+
s, c = sincos2(rad)
|
|
508
|
+
r = atan2(s, c)
|
|
509
|
+
n = round((rad - r) / PI2)
|
|
510
|
+
else:
|
|
511
|
+
_raiseError(Ang.fromRadians, rad, **unit_name)
|
|
512
|
+
a = Ang(s, c, n, **_xkwds(unit_name, unit=Radians))
|
|
513
|
+
a.__dict__.update(radians0=r) # Property_RO
|
|
514
|
+
return a
|
|
515
|
+
|
|
516
|
+
@staticmethod
|
|
517
|
+
def fromScalar(ang, **unit_name):
|
|
518
|
+
'''Get an C{Ang} from C{Degrees}, C{Radians} or another C{Ang}.
|
|
519
|
+
'''
|
|
520
|
+
if isAng(ang):
|
|
521
|
+
r = Ang(ang, **_xkwds(unit_name, unit=ang.unit))
|
|
522
|
+
else:
|
|
523
|
+
u = _xkwds_get(unit_name, unit=None)
|
|
524
|
+
if u is Lambertian:
|
|
525
|
+
r = Ang.fromLambertian(ang, **unit_name)
|
|
526
|
+
elif _isDegrees(ang, iscalar=u is Degrees):
|
|
527
|
+
r = Ang.fromDegrees(ang, **unit_name)
|
|
528
|
+
elif _isRadians(ang, iscalar=u is Radians):
|
|
529
|
+
r = Ang.fromRadians(ang, **unit_name)
|
|
530
|
+
else:
|
|
531
|
+
_raiseError(Ang.fromScalar, ang, **unit_name)
|
|
532
|
+
return r
|
|
533
|
+
|
|
534
|
+
def is_integer(self, *n):
|
|
535
|
+
'''Is this C{Ang}'s degrees C{integer}? (C{bool}).
|
|
536
|
+
'''
|
|
537
|
+
return self.toDegrees(*n).is_integer()
|
|
538
|
+
|
|
539
|
+
def isnear0(self, eps0=EPS0): # aka zerop
|
|
540
|
+
'''Is this C{Ang} near C{0} within a tolerance?
|
|
541
|
+
'''
|
|
542
|
+
s, c, n = self.scn3
|
|
543
|
+
return bool(n == 0 and c > 0 and fabs(s) <= eps0)
|
|
544
|
+
|
|
545
|
+
def _kwds(self, kwds, **dflt):
|
|
546
|
+
return _xkwds(kwds, **_xkwds(dflt, unit=self.unit,
|
|
547
|
+
name=self.name))
|
|
548
|
+
|
|
549
|
+
@Property_RO
|
|
550
|
+
def lambertian(self):
|
|
551
|
+
'''Get this C{Ang}'s Lambertian, C{asinh(tan(radians))}.
|
|
552
|
+
'''
|
|
553
|
+
return asinh(self.t) # XXX Lambertian(self.t)
|
|
554
|
+
|
|
555
|
+
def mod(self, mul=_1_0, **unit_name):
|
|
556
|
+
'''Return the I{reduced latitude} C{atan(B{mul} *
|
|
557
|
+
tan(B{this}))} as an C{Ang}.
|
|
558
|
+
|
|
559
|
+
@arg mul: Factor (C{scalar}, positive).
|
|
560
|
+
|
|
561
|
+
@note: The quadrant of the result tracks that of
|
|
562
|
+
this C{Ang} through multiples turns.
|
|
563
|
+
'''
|
|
564
|
+
kwds = self._kwds(unit_name)
|
|
565
|
+
if signBit(mul):
|
|
566
|
+
r = self._Ang(Ang.NAN(), **kwds)
|
|
567
|
+
else:
|
|
568
|
+
s, c, n = self.scn3
|
|
569
|
+
if mul > 1:
|
|
570
|
+
c = c / mul # /= chokes PyChecker
|
|
571
|
+
else: # mul <= 1
|
|
572
|
+
s *= mul
|
|
573
|
+
r = self._Ang(s, c, n, normal=False, **kwds)
|
|
574
|
+
return r
|
|
575
|
+
|
|
576
|
+
@staticmethod
|
|
577
|
+
def N(**unit_name):
|
|
578
|
+
'''Get North C{Ang}.
|
|
579
|
+
'''
|
|
580
|
+
return Ang(0, 1, **unit_name)
|
|
581
|
+
|
|
582
|
+
@property
|
|
583
|
+
def n(self):
|
|
584
|
+
'''Return the number of turns (C{float}) or C{0.0}.
|
|
585
|
+
'''
|
|
586
|
+
return self._n or _0_0
|
|
587
|
+
|
|
588
|
+
@n.setter # PYCHOK setter!
|
|
589
|
+
def n(self, n):
|
|
590
|
+
self._n_0(_fint(n))
|
|
591
|
+
|
|
592
|
+
def _n_0(self, n):
|
|
593
|
+
'''(INTERNAL) Set C{n} or C{n0}.
|
|
594
|
+
'''
|
|
595
|
+
if self._n != n:
|
|
596
|
+
self._n, n = n, self._n
|
|
597
|
+
self._update()
|
|
598
|
+
return n
|
|
599
|
+
|
|
600
|
+
@property
|
|
601
|
+
def n0(self):
|
|
602
|
+
'''Return the number of turns, treating C{-180} as C{180 - 1 turn} (C{float}).
|
|
603
|
+
'''
|
|
604
|
+
return (self.n - self._n01) or _0_0
|
|
605
|
+
|
|
606
|
+
@n0.setter # PYCHOK setter!
|
|
607
|
+
def n0(self, n):
|
|
608
|
+
self._n_0(_fint(n) + self._n01)
|
|
609
|
+
|
|
610
|
+
@Property_RO
|
|
611
|
+
def _n01(self):
|
|
612
|
+
s, c = self.sc2
|
|
613
|
+
return int(c < 0 and s == 0 and signBit(s))
|
|
614
|
+
|
|
615
|
+
@staticmethod
|
|
616
|
+
def NAN(**unit_name):
|
|
617
|
+
'''Get an invalid C{Ang}.
|
|
618
|
+
'''
|
|
619
|
+
return Ang(NAN, NAN, **unit_name)
|
|
620
|
+
|
|
621
|
+
@Property_RO
|
|
622
|
+
def ncardinal(self):
|
|
623
|
+
'''Get the nearest cardinal direction (C{float_int}).
|
|
624
|
+
|
|
625
|
+
@note: This is the reverse of C{cardinal}.
|
|
626
|
+
'''
|
|
627
|
+
return _ncardinal(*self.scn3)
|
|
628
|
+
|
|
629
|
+
def nearest(self, ind=0, **name):
|
|
630
|
+
'''Return the closest cardinal direction (C{Ang}).
|
|
631
|
+
|
|
632
|
+
@arg ind: An indicator, if C{B{ind}=0} the closest cardinal
|
|
633
|
+
direction, otherwise, if B{C{ind}} is even, the
|
|
634
|
+
closest even (N/S) cardinal direction or if B{C{ind}}
|
|
635
|
+
is odd, the closest odd (E/W) cardinal direction.
|
|
636
|
+
'''
|
|
637
|
+
s, c, n = self.scn3
|
|
638
|
+
p = (ind == 0 and fabs(s) > fabs(c)) or (ind & 1)
|
|
639
|
+
s, c = _orthogonal2(p, s, c)
|
|
640
|
+
return self._Ang(s, c, n, **self._kwds(name))
|
|
641
|
+
|
|
642
|
+
@staticmethod
|
|
643
|
+
def _norm(bet, omg, alp=None, alt=False):
|
|
644
|
+
'''(INTERNAL) Put C{bet}, C{ong} and C{alp} in range.
|
|
645
|
+
''' # Ellipsoid3.AngNorm
|
|
646
|
+
flip = signBit(omg.s if alt else bet.c)
|
|
647
|
+
if flip:
|
|
648
|
+
Ang._flip(bet, omg, alp)
|
|
649
|
+
return flip
|
|
650
|
+
|
|
651
|
+
def normalize(self, *n):
|
|
652
|
+
'''Re-normalize this C{Ang}, optionally replacing turns.
|
|
653
|
+
'''
|
|
654
|
+
sc = _normalize2(*self.sc2)
|
|
655
|
+
if n:
|
|
656
|
+
self.n, n = n[0], self.n
|
|
657
|
+
if self.n != n: # updated
|
|
658
|
+
self._s, self._c = sc
|
|
659
|
+
return self
|
|
660
|
+
return self._update(*sc)
|
|
661
|
+
|
|
662
|
+
def _other(self, other):
|
|
663
|
+
# get C{other} as C{Ang} from C{unit}
|
|
664
|
+
return other if isAng(other) else _other(other, self.unit)
|
|
665
|
+
|
|
666
|
+
pow = __pow__
|
|
667
|
+
|
|
668
|
+
@Property_RO
|
|
669
|
+
def _quadrant(self):
|
|
670
|
+
s, c = map(int, map(signBit, self.sc2))
|
|
671
|
+
return s + s + (c ^ s)
|
|
672
|
+
|
|
673
|
+
@property_doc_("this C{Ang}'s quadrant (C{int} 0..3)")
|
|
674
|
+
def quadrant(self):
|
|
675
|
+
return self._quadrant
|
|
676
|
+
|
|
677
|
+
@quadrant.setter # PYCHOK setter!
|
|
678
|
+
def quadrant(self, quadrant):
|
|
679
|
+
s, c = map(fabs, self.sc2)
|
|
680
|
+
q = int(quadrant)
|
|
681
|
+
if (q & 2):
|
|
682
|
+
s = -s # _copysign(self.s, -1 if (q & 2) else 1)
|
|
683
|
+
if (((q >> 1) ^ q) & 1):
|
|
684
|
+
c = -c # _copysign(self.c, -1 if (((q >> 1) ^ q) & 1) else 1)
|
|
685
|
+
self._update(s, c)
|
|
686
|
+
|
|
687
|
+
@Property_RO
|
|
688
|
+
def radians(self):
|
|
689
|
+
'''Get this C{Ang} in C{radians}.
|
|
690
|
+
'''
|
|
691
|
+
r = self.radians0
|
|
692
|
+
if self.n:
|
|
693
|
+
r += self.n * PI2
|
|
694
|
+
return r # XXX Radians(r, self.name)
|
|
695
|
+
|
|
696
|
+
@Property_RO
|
|
697
|
+
def radians0(self):
|
|
698
|
+
'''Get this C{Ang} in C{radians} ignoring the turns.
|
|
699
|
+
'''
|
|
700
|
+
return atan2(*self.sc2) # XXX Radians(r, self.name)
|
|
701
|
+
|
|
702
|
+
def reflect(self, flips=False, flipc=False, swapsc=False):
|
|
703
|
+
'''Reflect this C{Ang} in various ways.
|
|
704
|
+
|
|
705
|
+
@kwarg flips: Flip the sign of C{s}.
|
|
706
|
+
@kwarg flipc: Flip the sign of C{c}.
|
|
707
|
+
@kwarg swapsc: Swap C{s} and C{c}.
|
|
708
|
+
|
|
709
|
+
@note: The operations are carried out in the order
|
|
710
|
+
of the arguments.
|
|
711
|
+
'''
|
|
712
|
+
s, c = self.sc2
|
|
713
|
+
if flips:
|
|
714
|
+
s = -s
|
|
715
|
+
if flipc:
|
|
716
|
+
c = -c
|
|
717
|
+
if swapsc:
|
|
718
|
+
s, c = c, s
|
|
719
|
+
return self._update(s, c)
|
|
720
|
+
|
|
721
|
+
def round(self, *ndigits, **name):
|
|
722
|
+
'''Return this C{Ang}, optionally rounded to C{ndigits} (C{Ang}).
|
|
723
|
+
'''
|
|
724
|
+
s, c, n = self.scn3
|
|
725
|
+
if ndigits:
|
|
726
|
+
s = round(s, *ndigits)
|
|
727
|
+
c = round(c, *ndigits)
|
|
728
|
+
else:
|
|
729
|
+
s, c = map1(_rnd, s, c)
|
|
730
|
+
return self._Ang(s, c, n, **self._kwds(name))
|
|
731
|
+
|
|
732
|
+
@property_RO
|
|
733
|
+
def s(self):
|
|
734
|
+
'''Get the sine of this C{Ang} (C{float}).
|
|
735
|
+
'''
|
|
736
|
+
return self._s
|
|
737
|
+
|
|
738
|
+
@property_RO
|
|
739
|
+
def sc2(self):
|
|
740
|
+
'''Get the 2-tuple C{(s, c)}.
|
|
741
|
+
'''
|
|
742
|
+
return self.s, self.c
|
|
743
|
+
|
|
744
|
+
@Property_RO
|
|
745
|
+
def scn3(self):
|
|
746
|
+
'''Get the 3-tuple C{(s, c, n)}.
|
|
747
|
+
'''
|
|
748
|
+
return self.s, self.c, self.n
|
|
749
|
+
|
|
750
|
+
@property_RO
|
|
751
|
+
def scnu4(self):
|
|
752
|
+
'''Get the 4-tuple C{(s, c, n, unit)}.
|
|
753
|
+
'''
|
|
754
|
+
return self.s, self.c, self.n, self.unit
|
|
755
|
+
|
|
756
|
+
def shift(self, q=0, **unit_name):
|
|
757
|
+
'''Shift this C{Ang} by C{q} I{quarter} turns (C{scalar}).
|
|
758
|
+
'''
|
|
759
|
+
kwds = self._kwds(unit_name)
|
|
760
|
+
if _isfinite(q):
|
|
761
|
+
s = self.copy(**kwds)
|
|
762
|
+
if q:
|
|
763
|
+
s -= Ang.cardinal(q)
|
|
764
|
+
else:
|
|
765
|
+
s = Ang.NAN(**kwds)
|
|
766
|
+
return s
|
|
767
|
+
|
|
768
|
+
def signOf(self, *n):
|
|
769
|
+
'''Determine this C{Ang}'s sign, optionally replacing the turns.
|
|
770
|
+
|
|
771
|
+
@return: The sign (C{int}, -1, 0 or +1).
|
|
772
|
+
'''
|
|
773
|
+
return _signOf(self.toDegrees(*n), 0)
|
|
774
|
+
|
|
775
|
+
@Property_RO
|
|
776
|
+
def t(self):
|
|
777
|
+
'''Get the tangent of this C{Ang} (C{float}).
|
|
778
|
+
'''
|
|
779
|
+
return _over(*self.sc2)
|
|
780
|
+
|
|
781
|
+
def toDegrees(self, *n):
|
|
782
|
+
'''Return this C{Ang} as C{Degrees}, optionally replacing the turns.
|
|
783
|
+
'''
|
|
784
|
+
if n:
|
|
785
|
+
d = self.degrees0
|
|
786
|
+
n = float(n[0])
|
|
787
|
+
if n:
|
|
788
|
+
d += n * _360_0
|
|
789
|
+
else:
|
|
790
|
+
d = self.degrees
|
|
791
|
+
return Degrees(d, self.name)
|
|
792
|
+
|
|
793
|
+
def toLambertian(self, **name):
|
|
794
|
+
'''Return this C{Ang} as L{Lambertian}.
|
|
795
|
+
'''
|
|
796
|
+
name = _xkwds(name, name=self.name)
|
|
797
|
+
return Lambertian(self.lambertian, **name)
|
|
798
|
+
|
|
799
|
+
def toRadians(self, *n):
|
|
800
|
+
'''Return this C{Ang} as C{Radians}, optionally replacing the turns.
|
|
801
|
+
'''
|
|
802
|
+
if n:
|
|
803
|
+
r = self.radians0
|
|
804
|
+
n = float(n[0])
|
|
805
|
+
if n:
|
|
806
|
+
r += n * PI2
|
|
807
|
+
else:
|
|
808
|
+
r = self.radians
|
|
809
|
+
return Radians(r, self.name)
|
|
810
|
+
|
|
811
|
+
def toRepr(self, *n, **prec_fmt): # PYCHOK signature
|
|
812
|
+
'''Return this C{Ang} as C{"<name>(<value>)"} with/out turns (C{str}).
|
|
813
|
+
'''
|
|
814
|
+
return self.toUnit(*n).toRepr(**prec_fmt)
|
|
815
|
+
|
|
816
|
+
def toStr(self, *n, **prec_fmt): # PYCHOK signature
|
|
817
|
+
'''Return this C{Ang} as C{"<value>"} with/out turns (C{str}).
|
|
818
|
+
'''
|
|
819
|
+
return self.toUnit(*n).toStr(**prec_fmt)
|
|
820
|
+
|
|
821
|
+
def toTuple(self, **prec_fmt_sep):
|
|
822
|
+
'''Return string C{"(s, c, n)"} or tuple C{('s', 'c', 'n')} if C{sep is None}.
|
|
823
|
+
'''
|
|
824
|
+
return fstr(self.scn3, **prec_fmt_sep)
|
|
825
|
+
|
|
826
|
+
def toUnit(self, *n):
|
|
827
|
+
'''Return this C{Ang} as C{self.unit}s, optionally replacing the turns.
|
|
828
|
+
'''
|
|
829
|
+
u = self.unit
|
|
830
|
+
return self.toRadians(*n) if u is Radians else (
|
|
831
|
+
self.toDegrees(*n) if u is Degrees else (
|
|
832
|
+
self.toLambertian() if u is Lambertian else
|
|
833
|
+
_raiseError(self.toUnit, u))) # PYCHOK indent
|
|
834
|
+
|
|
835
|
+
@property_doc_(' the scalar unit to L{Degrees} or L{Radians}')
|
|
836
|
+
def unit(self):
|
|
837
|
+
return self._unit
|
|
838
|
+
|
|
839
|
+
@unit.setter # PYCHOK setter!
|
|
840
|
+
def unit(self, unit):
|
|
841
|
+
if unit not in _Ang_types: # PYCHOK no cover
|
|
842
|
+
_raiseError(Ang.unit, unit)
|
|
843
|
+
if self._unit != unit:
|
|
844
|
+
self._unit = unit
|
|
845
|
+
|
|
846
|
+
def _update(self, *sc):
|
|
847
|
+
if sc:
|
|
848
|
+
if sc == self.sc2:
|
|
849
|
+
return self
|
|
850
|
+
self._s, self._c = sc
|
|
851
|
+
_update_all(self)
|
|
852
|
+
return self
|
|
853
|
+
|
|
854
|
+
_allPropertiesOf_n(14, Ang) # PYCHOK assert
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
class _Ang3Tuple(_NamedTuple):
|
|
858
|
+
'''(INTERNAL) Methods C{.toDegrees}, C{.toLambertian}, C{.toRadians} and C{.toUnit}.
|
|
859
|
+
'''
|
|
860
|
+
_Names_ = (Ang.__name__,) * 3 # needed for ...
|
|
861
|
+
_Units_ = Ang, Ang, _Pass # ...testNamedTuples
|
|
862
|
+
|
|
863
|
+
def toDegrees(self, *n, **fmt_prec_sep):
|
|
864
|
+
'''Change any C{Ang} to C{unit Degrees} or to C{Degrees.toStr} if any B{C{fmt_prec_sep}}.
|
|
865
|
+
'''
|
|
866
|
+
t = self.toUnit(Degrees, *n)
|
|
867
|
+
if fmt_prec_sep: # see C{Degrees.toStr}
|
|
868
|
+
sep, fmt_prec = _xkwds_pop2(fmt_prec_sep, sep=_COMMASPACE_)
|
|
869
|
+
s = self.toStr(sep=None) if sep else self
|
|
870
|
+
t = (a.toStr(**fmt_prec) if isAng(a) else s for a, s in zip(t, s))
|
|
871
|
+
t = Fmt.PAREN(sep.join(t)) if sep else tuple(t)
|
|
872
|
+
return t
|
|
873
|
+
|
|
874
|
+
def toLambertian(self):
|
|
875
|
+
'''Change any C{Ang} to C{unit Lambertian}.
|
|
876
|
+
'''
|
|
877
|
+
return self.toUnit(Lambertian)
|
|
878
|
+
|
|
879
|
+
def toRadians(self, *n):
|
|
880
|
+
'''Change any C{Ang} to C{unit Radians}.
|
|
881
|
+
'''
|
|
882
|
+
return self.toUnit(Radians, *n)
|
|
883
|
+
|
|
884
|
+
def toUnit(self, unit, *n):
|
|
885
|
+
'''Change any C{Ang} to C{unit}, .
|
|
886
|
+
'''
|
|
887
|
+
for a in self:
|
|
888
|
+
if isAng(a): # and a.unit is not unit:
|
|
889
|
+
a.unit = unit
|
|
890
|
+
if n:
|
|
891
|
+
a.n = n[0]
|
|
892
|
+
return self
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
class Lambertian(Radians):
|
|
896
|
+
'''A C{Lambertian} in C{radians}.
|
|
897
|
+
'''
|
|
898
|
+
def __new__(cls, *args, **kwds):
|
|
899
|
+
return Radians.__new__(cls, *args, **_xkwds(kwds, name='psi'))
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
_Ang_from = {Radians: Ang.fromRadians,
|
|
903
|
+
Degrees: Ang.fromDegrees,
|
|
904
|
+
Lambertian: Ang.fromLambertian}
|
|
905
|
+
_Ang_types = tuple(_Ang_from.keys()) # PYCHOK used!
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
def Ang_(s, c=None, n=1, **unit_name):
|
|
909
|
+
'''(INTERNAL) New, non-normal C{Ang}.
|
|
910
|
+
'''
|
|
911
|
+
return Ang(s, c, n, **_xkwds(unit_name, normal=False))
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
def Deg(deg, **name):
|
|
915
|
+
'''Return an L{Ang} from C{deg} degrees or an other L{Ang}.
|
|
916
|
+
'''
|
|
917
|
+
return Ang(deg, unit=Degrees, **name)
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def isAng(ang):
|
|
921
|
+
'''Is C{ang} an L{Ang} instance?
|
|
922
|
+
'''
|
|
923
|
+
return isinstance(ang, Ang)
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def Rad(rad, **name):
|
|
927
|
+
'''Return an L{Ang} from C{rad} radians or an other L{Ang}.
|
|
928
|
+
'''
|
|
929
|
+
return Ang(rad, unit=Radians, **name)
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def _SinCos2(ang, *unit):
|
|
933
|
+
'''Get C{sin} and C{cos} of an L{Ang}, any I{typed} C{ang}le
|
|
934
|
+
or C{unit} if C{ang}le is scalar.
|
|
935
|
+
|
|
936
|
+
@see: Function L{SinCos2<pygeodesy.utily.SinCos2>}.
|
|
937
|
+
'''
|
|
938
|
+
return ang.sc2 if isAng(ang) else SinCos2(ang, *unit)
|
|
939
|
+
|
|
940
|
+
# **) MIT License
|
|
941
|
+
#
|
|
942
|
+
# Copyright (C) 2025-2026 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
943
|
+
#
|
|
944
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
945
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
946
|
+
# to deal in the Software without restriction, including without limitation
|
|
947
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
948
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
949
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
950
|
+
#
|
|
951
|
+
# The above copyright notice and this permission notice shall be included
|
|
952
|
+
# in all copies or substantial portions of the Software.
|
|
953
|
+
#
|
|
954
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
955
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
956
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
957
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
958
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
959
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
960
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|