Coverage for pygeodesy/auxilats/auxily.py: 94%
107 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-04-09 11:05 -0400
« prev ^ index » next coverage.py v7.6.1, created at 2025-04-09 11:05 -0400
2# -*- coding: utf-8 -*-
4u'''(INTERNAL) I{Auxiliary} latitudes' classes, constants and functions.
6Class L{AuxAngle} transcoded to Python from I{Karney}'s C++ class U{AuxAngle
7<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1AuxAngle.html>}
8in I{GeographicLib version 2.2+}.
10Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2022-2024) and licensed
11under the MIT/X11 License. For more information, see the U{GeographicLib
12<https://GeographicLib.SourceForge.io>} documentation.
13'''
14# make sure int/int division yields float quotient, see .basics
15from __future__ import division as _; del _ # PYCHOK semicolon
17# from pygeodesy import auxilats # _MODS
18from pygeodesy.auxilats._CX_Rs import _Rkey
19from pygeodesy.constants import INF, NAN, isinf, isnan, _0_0, _0_5, _1_0, \
20 _copysign_1_0, _over, _1_over
21from pygeodesy.errors import AuxError
22from pygeodesy.fmath import hypot1 as _sc, hypot2_
23from pygeodesy.interns import NN, _DOT_, _UNDER_ # PYCHOK used!
24from pygeodesy.lazily import _ALL_DOCS, _ALL_MODS as _MODS # PYCHOK used!
25from pygeodesy.utily import atan1
27from math import asinh, copysign
29__all__ = ()
30__version__ = '25.01.15'
33class Aux(object):
34 '''Enum-style Aux names.
35 '''
36 _coeffs = {}
37 GEOGRAPHIC = PHI = GEODETIC = _Rkey(0)
38 PARAMETRIC = BETA = REDUCED = _Rkey(1)
39 GEOCENTRIC = THETA = _Rkey(2) # all ...
40 RECTIFYING = MU = _Rkey(3) # use n^2
41 CONFORMAL = CHI = _Rkey(4) # use n
42 AUTHALIC = XI = _Rkey(5) # use n
43 N = 6
44 N2 = 36
46 def __index__(self, aux):
47 # throws KeyError, not IndexError
48 return _Aux2Greek[aux]
50 def __len__(self):
51 return Aux.N
53 def _CXcoeffs(self, aL): # in .auxLat.AuxLat._CXcoeffs
54 '''(INTERNAL) Get the C{_CX_4._coeffs_4}, C{_CX_6._coeffs_6}
55 or C{_CS_8._coeffs_8} coefficients, once.
56 '''
57 try:
58 _coeffs = Aux._coeffs[aL]
59 except KeyError:
60 try: # from pygeodesy.auxilats._CX_x import _coeffs_x as _coeffs
61 _CX_x = _DOT_(_MODS.auxilats.__name__, _UNDER_('_CX', aL))
62 _coeffs = _MODS.getattr(_CX_x, _UNDER_('_coeffs', aL))
63 except (AttributeError, ImportError, KeyError, TypeError) as x:
64 raise AuxError(ALorder=aL, cause=x)
65 Aux._coeffs[aL] = _coeffs._validate(aL, Aux.len(aL))
66 return _coeffs
68 def _1d(self, auxout, auxin):
69 '''Get the 1-d index into N^2 coeffs.
70 '''
71 N = Aux.N
72 if 0 <= auxout < N and 0 <= auxin < N:
73 return N * auxout + auxin
74 raise AuxError(auxout=auxout, auxin=auxin, N=N)
76 def Greek(self, aux):
77 '''Get an angle's name (C{str}).
78 '''
79 return _Aux2Greek.get(aux, NN)
81 def len(self, ALorder): # PYCHOK no cover
82 aL = ALorder # aka Lmax
83 mu = Aux.MU * (Aux.MU + 1)
84 nu = Aux.N2 - Aux.N - mu
85 return (mu * (aL * (aL + 3) - (aL // 2) * 2) // 4 +
86 nu * (aL * (aL + 1)) // 2)
88 def power(self, auxout, auxin):
89 '''Get the C{convert} exponent (C{int} or C{None}).
90 '''
91 self._1d(auxout, auxin) # validate
92 return (auxout - auxin) if max(auxin, auxout) < Aux.MU else None
94 def use_n2(self, aux):
95 return aux not in (Aux.CHI, Aux.XI)
97Aux = Aux() # PYCHOK singleton
99_Aux2Greek = {Aux.AUTHALIC: 'Xi',
100 Aux.CONFORMAL: 'Chi',
101 Aux.GEOCENTRIC: 'Theta',
102 Aux.GEODETIC: 'Phi', # == .GEOGRAPHIC
103 Aux.PARAMETRIC: 'Beta', # == .REDUCED
104 Aux.RECTIFYING: 'Mu'}
105_Greek2Aux = dict(map(reversed, _Aux2Greek.items())) # PYCHOK exported
106# _Greek2Aux.update((_g.upper(), _x) for _g, _x in _Greek2Aux.items())
109def _Dasinh(x, y):
110 d = y - x
111 if isinf(d): # PYCHOK no cover
112 r = _0_0
113 elif isnan(d): # PYCHOK no cover
114 r = NAN
115 elif d:
116 xy = x * y
117 if xy > 0:
118 hx, hy = _sc(x), _sc(y)
119 if xy < 1:
120 hx, hy = hy, hx
121 else:
122 x = _1_0 / x
123 y = _1_0 / y
124 r = _over(x + y, hx * x + hy * y)
125 r = asinh(r * d) / d
126 else:
127 r = (asinh(y) - asinh(x)) / d
128 else:
129 r = _1_over(_sc(x))
130 return r
133def _Datan(x, y):
134 xy = x * y
135 r = xy + _1_0
136 if isnan(r): # PYCHOK no cover
137 pass
138 elif x == y:
139 r = _1_over(r)
140 elif x > 0 and isinf(xy): # PYCHOK no cover
141 r = _0_0
142 else:
143 d = y - x
144 if (r + xy) > 0:
145 r = atan1(d, r) / d # atan(d / r) / d
146 else:
147 r = (atan1(y) - atan1(x)) / d
148 return r
151def _Dh(x, y):
152 r = x + y
153 if isnan(r):
154 pass # N.B. NAN for inf-inf
155 elif isinf(x): # PYCHOK no cover
156 r = copysign(_0_5, x)
157 elif isinf(y): # PYCHOK no cover
158 r = copysign(_0_5, y)
159 else:
160 snx, sny = _sn(x), _sn(y)
161 dy = sny * y
162 dx = snx * x
163 d = dy + dx
164 if (d * _0_5):
165 if (x * y) > 0:
166 r *= hypot2_(snx / _sc(y), snx * sny,
167 sny / _sc(x)) / (d + d)
168 else:
169 r = _over((dy - dx) * _0_5, y - x)
170 else: # underflow and x == y == d == 0
171 r *= _0_5 # PYCHOK no cover
172 return r
175def _Dlam(x, y): # Chi1.tan, Chi2.tan
176 # I{Divided difference} of the isometric latitude
177 # with respect to the conformal latitude
178 if isnan(x) or isnan(y): # PYCHOK no cover
179 r = NAN
180 elif isinf(x) or isinf(y): # PYCHOK no cover
181 r = INF
182 elif x == y:
183 r = _sc(x)
184 else:
185 r = _over(_Dasinh(x, y), _Datan(x, y))
186 return r
189def _Dm(X, Y, s): # in .auxDLat, .auxDST
190 # Return M{(X - Y) * s}, inplace X
191 X -= Y
192 X *= s
193 return X # Fsum
196def _Dp0Dpsi(x, y): # Chi1.tan, Chi2.tan
197 # I{Divided difference} of the spherical rhumb area
198 # term with respect to the isometric latitude
199 r = x + y
200 if isnan(r): # PYCHOK no cover
201 pass # NAN for inf-inf
202 elif isinf(x): # PYCHOK no cover
203 r = _copysign_1_0(x)
204 elif isinf(y): # PYCHOK no cover
205 r = _copysign_1_0(y)
206 elif x == y:
207 r = _sn(x)
208 else:
209 r = _Dasinh(_h(x), _h(y))
210 r = _over(_Dh(x, y) * r, _Dasinh(x, y))
211 return r
214def _h(tx):
215 '''(INTERNAL) M{h(tan(x)) = tan(x) * sin(x) / 2}
216 '''
217 if tx:
218 tx *= _sn(tx) * _0_5
219 return tx # preserve signed-0
222def _sn(tx):
223 '''(INTERNAL) M{sin(x) = tan(x) / sqrt(tan(x)**2 + 1)}.
224 '''
225 if tx:
226 tx = _copysign_1_0(tx) if isinf(tx) else (
227 NAN if isnan(tx) else (tx / _sc(tx)))
228 return tx # preserve signed-0
231__all__ += _ALL_DOCS(Aux.__class__)
232del _Rkey
234# **) MIT License
235#
236# Copyright (C) 2023-2025 -- mrJean1 at Gmail -- All Rights Reserved.
237#
238# Permission is hereby granted, free of charge, to any person obtaining a
239# copy of this software and associated documentation files (the "Software"),
240# to deal in the Software without restriction, including without limitation
241# the rights to use, copy, modify, merge, publish, distribute, sublicense,
242# and/or sell copies of the Software, and to permit persons to whom the
243# Software is furnished to do so, subject to the following conditions:
244#
245# The above copyright notice and this permission notice shall be included
246# in all copies or substantial portions of the Software.
247#
248# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
249# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
250# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
251# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
252# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
253# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
254# OTHER DEALINGS IN THE SOFTWARE.