1# -*- coding: utf-8 -*-

2

3 u'''Class L{AuxDLat} transcoded to Python from I{Karney}'s C++ class U{DAuxLatitude

4<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1DAuxLatitude.html>}

5 in I{GeographicLib version 2.2+}.

6

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

8 under the MIT/X11 License. For more information, see the U{GeographicLib

9<https://GeographicLib.SourceForge.io>} documentation.

10'''

11# make sure int/int division yields float quotient, see .basics

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

13

14 from pygeodesy.auxilats.auxily import Aux, _Datan, _Dasinh, _Dm, _sc, _sn, \

15 AuxError

16 from pygeodesy.auxilats.auxLat import AuxLat, _ALL_DOCS

17 from pygeodesy.basics import map1, _reverange

18 from pygeodesy.constants import INF, NAN, isfinite, isinf, isnan, _0_0, _0_5, \

19 _1_0, _2_0, _N_2_0, _naninf, _over, _1_over

20 from pygeodesy.elliptic import Elliptic as _Ef, Fsum

21# from pygeodesy.errors import AuxError # from .auxilats.auxily

22# from pygeodesy.fsums import Fsum # from .elliptic

23# from pygeodesy.lazily import _ALL_DOCS # from .auxilats.auxLat

24 from pygeodesy.utily import atan1, atan2 # from .auxilats.auxily

25

26 from math import cos, sin, sqrt

27

28__all__ = ()

29__version__ = '24.11.24'

30

31

32 class AuxDLat(AuxLat):

33 '''Class to compute C{Divided Differences} of I{Auxiliary}

34 latitudes and other C{Divided Differences} needed for

35 L{RhumbAux} and L{RhumbLineAux} calculations.

36 '''

37

38 def CParametric(self, Zeta1, Zeta2):

39 '''Short for C{.Dconvert(Aux.BETA, B{Zeta1}, B{Zeta2})}.

40 '''

41 return self.Dconvert(Aux.BETA, Zeta1, Zeta2)

42

43 def CRectifying(self, Zeta1, Zeta2):

44 '''Short for C{.Dconvert(Aux.MU, B{Zeta1}, B{Zeta2})}.

45 '''

46 return self.Dconvert(Aux.MU, Zeta1, Zeta2)

47

48 def _Datanhee(self, x, y):

49 # atan(e*sn(tphi))/e:

50 # Datan(e*sn(x),e*sn(y))*Dsn(x,y)/Datan(x,y)

51 # asinh(e1*sn(fm1*tphi)):

52 # Dasinh(e1*sn(fm1*x)), e1*sn(fm1*y)) *

53 # e1 * Dsn(fm1*x, fm1*y) *fm1 / (e * Datan(x,y))

54 # = Dasinh(e1*sn(fm1*x)), e1*sn(fm1*y)) *

55 # Dsn(fm1*x, fm1*y) / Datan(x,y)

56 if self.f < 0:

57 e = self._e

58 r = _Datan(e * _sn(x), e * _sn(y))

59 else:

60 x *= self._fm1

61 y *= self._fm1

62 e1 = self._e1

63 r = _Dasinh(e1 * _sn(x), e1 * _sn(y))

64 return _Dsn(x, y) * r

65

66 def Dconvert(self, auxout, Zeta1, Zeta2):

67 '''I{Divided Difference} of one auxiliary latitude wrt another.

68 '''

69 auxin = Zeta1._AUX

70 # assert Zeta2._AUX == auxin

71 try:

72 if auxout != auxin:

73 cs = self._coeffs(auxout, auxin)

74 # assert len(cs) == self.ALorder

75 r = _DClenshaw(True, Zeta1, Zeta2, cs, self.ALorder)

76 else:

77 r = _1_0

78 except AuxError: # no _coeffs

79 r = NAN

80 return r

81

82 def DE(self, X, Y):

83 # We assume that X and Y are in [-90d, 90d] and

84 # have the same sign. If not we would include

85 # if (Xn.y() * Yn.y() < 0)

86 # return d != 0 ? (E(X) - E(Y)) / d : 1

87 # The general formula fails for x = y = 0d and

88 # x = y = 90d. Probably this is fixable (the

89 # formula works for other x = y. But let's

90 # also stipulate that x != y.

91

92 # Make both y positive, so we can do the swap a <-> b trick

93 sx, cx, x = X._yxr_normalized(True)

94 sy, cy, y = Y._yxr_normalized(True)

95 k2, d = -self._e12, (y - x)

96 # Switch prolate to oblate, then use formulas for k2 < 0

97 if self.f < 0: # XXX and False?

98 sx, cx = cx, sx

99 sy, cy = cy, sy

100 d, k2 = -d, self._e2

101 # See DLMF: Eqs (19.11.2) and (19.11.4) letting

102 Dt = _Dsin(x, y) * (sx + sy)

103 if Dt:

104 t = _sxk2y(sx, sy, k2) + _sxk2y(sy, sx, k2)

105 Dt = _over(Dt, t * (cx + cy))

106 t = d * Dt

107 t2 = _1_0 + t**2

108 Dt *= _2_0 / t2

109 sk2 = (d * Dt)**2 * k2

110 d2 = _1_0 - sk2

111 c2 = ((_1_0 - t) * (_1_0 + t) / t2)**2 if t else _1_0

112 # E(z)/sin(z)

113 Dt *= _Ef._RFRD(c2, d2, _1_0, sk2) - k2 * sx * sy

114 return Dt

115

116 def DIsometric(self, Phi1, Phi2):

117 '''I{Divided Difference} of the isometric wrt the geographic latitude.

118 '''

119 tx, ty = Phi1.tan, Phi2.tan

120 if isnan(ty) or isnan(tx): # PYCHOK no cover

121 r = NAN

122 elif isinf(ty) or isinf(tx): # PYCHOK no cover

123 r = INF

124 else: # psi = asinh(tan(Phi)) - e^2 * atanhee(tan(Phi))

125 r = self._Datanhee(tx, ty) * self._e2

126 r = _over(_Dasinh(tx, ty) - r, _Datan(tx, ty))

127 return r

128

129 def DParametric(self, Phi1, Phi2):

130 '''I{Divided Difference} of the parametric wrt the geographic latitude.

131 '''

132 fm1, e2m1 = self._fm1, self._e2m1

133 tx, ty = Phi1.tan, Phi2.tan

134 # DbetaDphi = Datan(fm1*tx, fm1*ty) * fm1 / Datan(tx, ty)

135 # Datan(x, y) = 1 / (1 + x^2) if x == y

136 # = (atan1(y) - atan1(x)) / (y-x) if x*y < 0

137 # = atan1(y-x, x*y + 1) / (y-x) if x*y > 0

138 txy = tx * ty

139 if txy < 0 or (isinf(ty) and not tx):

140 _a = atan1

141 r = _a(fm1 * ty) - _a(fm1 * tx)

142 r = _over(r, _a(ty) - _a(tx))

143 elif tx == ty: # includes tx = ty = inf

144 if txy > 1: # == tx**2

145 txy = _1_over(txy)

146 r = txy + e2m1

147 else:

148 r = txy * e2m1 + _1_0

149 r = _over((txy + _1_0) * fm1, r)

150 else:

151 if txy > 1:

152 tx = _1_over(tx)

153 ty = _1_over(ty)

154 txy = _1_over(txy)

155 t = txy + e2m1

156 else:

157 t = txy * e2m1 + _1_0

158 r = ty - tx

159 r = _over(atan2(r * fm1, t), atan2(r, txy + _1_0))

160 return r

161

162 def DRectifying(self, Phi1, Phi2):

163 '''I{Divided Difference} of the rectifying wrt the geographic latitude.

164 '''

165 # Stipulate that Phi1 and Phi2 are in [-90d, 90d]

166 x, y = Phi1.toRadians, Phi2.toRadians

167 if y == x: # isnear0

168 Mu1 = self.Rectifying(Phi1, diff=True)

169 tphi1, r = Phi1.tan, Mu1.diff

170 if isfinite(tphi1):

171 r *= _over(_sc(tphi1), _sc(Mu1.tan))**2

172 else: # PYCHOK no cover

173 r = _1_over(r)

174 elif (x * y) < 0:

175 r = _over(self.Rectifying(Phi2).toRadians -

176 self.Rectifying(Phi1).toRadians, y - x)

177 else:

178 r = _over(self.b, self.RectifyingRadius(True))

179 r *= self.DE(*map1(self.Parametric, Phi1, Phi2))

180 r *= self.DParametric(Phi1, Phi2)

181 return r # or INF or NAN

182

183

184 def _DClenshaw(sinp, Zeta1, Zeta2, cs, K):

185 '''(INTERNAL) I{Divided Difference} of L{AuxLat._Clenshaw}.

186

187 @return: C{float} if B{C{sinp}} otherwise a C{Fsum}.

188 '''

189 s1, c1, r1 = Zeta1._yxr_normalized(False)

190 s2, c2, r2 = Zeta2._yxr_normalized(False)

191 Delta = r2 - r1

192 # Evaluate (Clenshaw(sinp, szeta2, czeta2, cs, K) -

193 # Clenshaw(sinp, szeta1, czeta1, cs, K)) / Delta

194 # or f = sin if sinp else cos

195 # sum(cs[k] * (f((2*k+2) * Zeta2) -

196 # f((2*k+2) * Zeta2))) / Delta

197 #

198 # Delta is EITHER 1, giving the plain difference OR (Zeta2 - Zeta1)

199 # in radians, giving the I{Divided Difference}. Other values will

200 # produce nonsense.

201 #

202 # Suffices a and b denote [1,1], [2,1] elements of matrix/vector

203 cp = cm = c2 * c1

204 t = s2 * s1

205 cp -= t # not +

206 cm += t # not -

207

208 sp = s2 * c1

209 t = c2 * s1

210 smd = ((sin(Delta) / Delta) if Delta != _1_0 else

211 (sp - t)) if Delta else _1_0

212 sp += t

213

214 xa = cp * cm * _2_0

215 xb = sp * smd * _N_2_0

216 xD = xb * Delta**2

217

218 if isfinite(xD) and isfinite(xb) and isfinite(xa):

219 U0a, U1a = Fsum(), Fsum()

220 U0b, U1b = Fsum(), Fsum()

221 for k in _reverange(K): # assert len(cs) == K

222 # t = x . U0 - U1 + cs[k] * I

223 U1a -= U0a * xa + U0b * xD + cs[k]

224 U1b -= U0a * xb + U0b * xa

225 U1a, U0a = U0a, -U1a

226 U1b, U0b = U0b, -U1b

227 # F0a = (sp if sinp else cp) * cm

228 # F0b = (cp if sinp else -sp) * smd

229 # Fm1a = 0 if sinp else 1 # Fm1b = 0

230 # return (U0b * F0a + U0a * F0b - U1b * Fm1a) * 2

231 if sinp:

232 U1b = _0_0

233 else:

234 sp, cp = cp, -sp

235 U0b *= sp * cm

236 U0a *= cp * smd

237 U0a += U0b

238 U0a = _Dm(U0a, U1b, _2_0)

239 r = float(U0a) if sinp else U0a # Fsum

240 else:

241 r = _naninf(xD, xb, xa)

242 return r

243

244

245 def _Dsin(x, y): # see also .rhumb.ekx._Dsin

246 r = cos((x + y) * _0_5)

247 d = (x - y) * _0_5

248 if d:

249 r *= sin(d) / d

250 return r

251

252

253 def _Dsn(x, y):

254 # (sn(y) - sn(x)) / (y - x)

255 if x != y:

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

257 if (x * y) > 0:

258 scx, scy = _sc(x), _sc(y)

259 r = _over((snx / scy) + (sny / scx),

260 (snx + sny) * scy * scx)

261 else:

262 r = (sny - snx) / (y - x)

263 elif x:

264 r = _1_over(_sc(x) * (x**2 + _1_0)) # == 1 / sqrt3(x**2 + 1)

265 else:

266 r = _1_0

267 return r

268

269

270 def _sxk2y(sx, sy, k2):

271 # .DE helper

272 sy *= sy * k2

273 if sy:

274 try:

275 sx *= sqrt(_1_0 - sy)

276 except ValueError: # domain error

277 sx = NAN

278 return sx

279

280

281__all__ += _ALL_DOCS(AuxDLat)

282

283# **) MIT License

284#

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

286#

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

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

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

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

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

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

293#

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

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

296#

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

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

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

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

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

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

303# OTHER DEALINGS IN THE SOFTWARE.