1

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

3

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

5

6 Class L{AuxAngle} transcoded to Python from I{Karney}'s C++ class U{AuxAngle

7<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1AuxAngle.html>}

8 in I{GeographicLib version 2.2+}.

9

10 Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2022-2024) and licensed

11 under 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

15 from __future__ import division as _; del _ # PYCHOK semicolon

16

17# from pygeodesy import auxilats # _MODS

18 from pygeodesy.auxilats._CX_Rs import _Rkey

19 from pygeodesy.basics import _isin, typename

20 from pygeodesy.constants import INF, NAN, isinf, isnan, _0_0, _0_5, _1_0, \

21 _copysign_1_0, _over, _1_over

22 from pygeodesy.errors import AuxError

23 from pygeodesy.fmath import hypot1 as _sc, hypot2_

24# from pygeodesy.internals import typename # from .basics

25 from pygeodesy.interns import NN, _DOT_, _UNDER_ # PYCHOK used!

26 from pygeodesy.lazily import _ALL_DOCS, _ALL_MODS as _MODS # PYCHOK used!

27 from pygeodesy.utily import atan1

28

29 from math import asinh, copysign

30

31__all__ = ()

32__version__ = '25.04.14'

33

34

35 class Aux(object):

36 '''Enum-style Aux names.

37 '''

38 _coeffs = {}

39 GEOGRAPHIC = PHI = GEODETIC = _Rkey(0)

40 PARAMETRIC = BETA = REDUCED = _Rkey(1)

41 GEOCENTRIC = THETA = _Rkey(2) # all ...

42 RECTIFYING = MU = _Rkey(3) # use n^2

43 CONFORMAL = CHI = _Rkey(4) # use n

44 AUTHALIC = XI = _Rkey(5) # use n

45 N = 6

46 N2 = 36

47

48 def __index__(self, aux):

49 # throws KeyError, not IndexError

50 return _Aux2Greek[aux]

51

52 def __len__(self):

53 return Aux.N

54

55 def _CXcoeffs(self, aL): # in .auxLat.AuxLat._CXcoeffs

56 '''(INTERNAL) Get the C{_CX_4._coeffs_4}, C{_CX_6._coeffs_6}

57 or C{_CS_8._coeffs_8} coefficients, once.

58 '''

59 try:

60 _coeffs = Aux._coeffs[aL]

61 except KeyError:

62 try: # from pygeodesy.auxilats._CX_x import _coeffs_x as _coeffs

63 _CX_x = _DOT_(typename(_MODS.auxilats), _UNDER_('_CX', aL))

64 _coeffs = _MODS.getattr(_CX_x, _UNDER_('_coeffs', aL))

65 except (AttributeError, ImportError, KeyError, TypeError) as x:

66 raise AuxError(ALorder=aL, cause=x)

67 Aux._coeffs[aL] = _coeffs._validate(aL, Aux.len(aL))

68 return _coeffs

69

70 def _1d(self, auxout, auxin):

71 '''Get the 1-d index into N^2 coeffs.

72 '''

73 N = Aux.N

74 if 0 <= auxout < N and 0 <= auxin < N:

75 return N * auxout + auxin

76 raise AuxError(auxout=auxout, auxin=auxin, N=N)

77

78 def Greek(self, aux):

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

80 '''

81 return _Aux2Greek.get(aux, NN)

82

83 def len(self, ALorder): # PYCHOK no cover

84 aL = ALorder # aka Lmax

85 mu = Aux.MU * (Aux.MU + 1)

86 nu = Aux.N2 - Aux.N - mu

87 return (mu * (aL * (aL + 3) - (aL // 2) * 2) // 4 +

88 nu * (aL * (aL + 1)) // 2)

89

90 def power(self, auxout, auxin):

91 '''Get the C{convert} exponent (C{int} or C{None}).

92 '''

93 self._1d(auxout, auxin) # validate

94 return (auxout - auxin) if max(auxin, auxout) < Aux.MU else None

95

96 def use_n2(self, aux):

97 return not _isin(aux, Aux.CHI, Aux.XI)

98

99 Aux = Aux() # PYCHOK singleton

100

101_Aux2Greek = {Aux.AUTHALIC: 'Xi',

102 Aux.CONFORMAL: 'Chi',

103 Aux.GEOCENTRIC: 'Theta',

104 Aux.GEODETIC: 'Phi', # == .GEOGRAPHIC

105 Aux.PARAMETRIC: 'Beta', # == .REDUCED

106 Aux.RECTIFYING: 'Mu'}

107_Greek2Aux = dict(map(reversed, _Aux2Greek.items())) # PYCHOK exported

108# _Greek2Aux.update((_g.upper(), _x) for _g, _x in _Greek2Aux.items())

109

110

111 def _Dasinh(x, y):

112 d = y - x

113 if isinf(d): # PYCHOK no cover

114 r = _0_0

115 elif isnan(d): # PYCHOK no cover

116 r = NAN

117 elif d:

118 xy = x * y

119 if xy > 0:

120 hx, hy = _sc(x), _sc(y)

121 if xy < 1:

122 hx, hy = hy, hx

123 else:

124 x = _1_0 / x

125 y = _1_0 / y

126 r = _over(x + y, hx * x + hy * y)

127 r = asinh(r * d) / d

128 else:

129 r = (asinh(y) - asinh(x)) / d

130 else:

131 r = _1_over(_sc(x))

132 return r

133

134

135 def _Datan(x, y):

136 xy = x * y

137 r = xy + _1_0

138 if isnan(r): # PYCHOK no cover

139 pass

140 elif x == y:

141 r = _1_over(r)

142 elif x > 0 and isinf(xy): # PYCHOK no cover

143 r = _0_0

144 else:

145 d = y - x

146 if (r + xy) > 0:

147 r = atan1(d, r) / d # atan(d / r) / d

148 else:

149 r = (atan1(y) - atan1(x)) / d

150 return r

151

152

153 def _Dh(x, y):

154 r = x + y

155 if isnan(r):

156 pass # N.B. NAN for inf-inf

157 elif isinf(x): # PYCHOK no cover

158 r = copysign(_0_5, x)

159 elif isinf(y): # PYCHOK no cover

160 r = copysign(_0_5, y)

161 else:

162 snx, sny = _sn(x), _sn(y)

163 dy = sny * y

164 dx = snx * x

165 d = dy + dx

166 if (d * _0_5):

167 if (x * y) > 0:

168 r *= hypot2_(snx / _sc(y), snx * sny,

169 sny / _sc(x)) / (d + d)

170 else:

171 r = _over((dy - dx) * _0_5, y - x)

172 else: # underflow and x == y == d == 0

173 r *= _0_5 # PYCHOK no cover

174 return r

175

176

177 def _Dlam(x, y): # Chi1.tan, Chi2.tan

178 # I{Divided difference} of the isometric latitude

179 # with respect to the conformal latitude

180 if isnan(x) or isnan(y): # PYCHOK no cover

181 r = NAN

182 elif isinf(x) or isinf(y): # PYCHOK no cover

183 r = INF

184 elif x == y:

185 r = _sc(x)

186 else:

187 r = _over(_Dasinh(x, y), _Datan(x, y))

188 return r

189

190

191 def _Dm(X, Y, s): # in .auxDLat, .auxDST

192 # Return M{(X - Y) * s}, inplace X

193 X -= Y

194 X *= s

195 return X # Fsum

196

197

198 def _Dp0Dpsi(x, y): # Chi1.tan, Chi2.tan

199 # I{Divided difference} of the spherical rhumb area

200 # term with respect to the isometric latitude

201 r = x + y

202 if isnan(r): # PYCHOK no cover

203 pass # NAN for inf-inf

204 elif isinf(x): # PYCHOK no cover

205 r = _copysign_1_0(x)

206 elif isinf(y): # PYCHOK no cover

207 r = _copysign_1_0(y)

208 elif x == y:

209 r = _sn(x)

210 else:

211 r = _Dasinh(_h(x), _h(y))

212 r = _over(_Dh(x, y) * r, _Dasinh(x, y))

213 return r

214

215

216 def _h(tx):

217 '''(INTERNAL) M{h(tan(x)) = tan(x) * sin(x) / 2}

218 '''

219 if tx:

220 tx *= _sn(tx) * _0_5

221 return tx # preserve signed-0

222

223

224 def _sn(tx):

225 '''(INTERNAL) M{sin(x) = tan(x) / sqrt(tan(x)**2 + 1)}.

226 '''

227 if tx:

228 tx = _copysign_1_0(tx) if isinf(tx) else (

229 NAN if isnan(tx) else (tx / _sc(tx)))

230 return tx # preserve signed-0

231

232

233__all__ += _ALL_DOCS(Aux.__class__)

234 del _Rkey

235

236# **) MIT License

237#

238# Copyright (C) 2023-2025 -- mrJean1 at Gmail -- All Rights Reserved.

239#

240# Permission is hereby granted, free of charge, to any person obtaining a

241# copy of this software and associated documentation files (the "Software"),

242# to deal in the Software without restriction, including without limitation

243# the rights to use, copy, modify, merge, publish, distribute, sublicense,

244# and/or sell copies of the Software, and to permit persons to whom the

245# Software is furnished to do so, subject to the following conditions:

246#

247# The above copyright notice and this permission notice shall be included

248# in all copies or substantial portions of the Software.

249#

250# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS

251# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

252# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL

253# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR

254# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,

255# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

256# OTHER DEALINGS IN THE SOFTWARE.