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

1 

2# -*- coding: utf-8 -*- 

3 

4u'''(INTERNAL) I{Auxiliary} latitudes' classes, constants and functions. 

5 

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+}. 

9 

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 

16 

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 

26 

27from math import asinh, copysign 

28 

29__all__ = () 

30__version__ = '25.01.15' 

31 

32 

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 

45 

46 def __index__(self, aux): 

47 # throws KeyError, not IndexError 

48 return _Aux2Greek[aux] 

49 

50 def __len__(self): 

51 return Aux.N 

52 

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 

67 

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) 

75 

76 def Greek(self, aux): 

77 '''Get an angle's name (C{str}). 

78 ''' 

79 return _Aux2Greek.get(aux, NN) 

80 

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) 

87 

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 

93 

94 def use_n2(self, aux): 

95 return aux not in (Aux.CHI, Aux.XI) 

96 

97Aux = Aux() # PYCHOK singleton 

98 

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()) 

107 

108 

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 

131 

132 

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 

149 

150 

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 

173 

174 

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 

187 

188 

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 

194 

195 

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 

212 

213 

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 

220 

221 

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 

229 

230 

231__all__ += _ALL_DOCS(Aux.__class__) 

232del _Rkey 

233 

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.