pygeodesy 24.3.24__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-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- pygeodesy/wgrs.py +439 -0
|
@@ -0,0 +1,302 @@
|
|
|
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-2023) 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
|
+
atan1, 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 # from .auxilats.auxily
|
|
25
|
+
|
|
26
|
+
from math import atan2, cos, sin, sqrt
|
|
27
|
+
|
|
28
|
+
__all__ = ()
|
|
29
|
+
__version__ = '23.12.01'
|
|
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 = _over(_a(fm1 * ty) - _a(fm1 * tx), _a(ty) - _a(tx))
|
|
142
|
+
elif tx == ty: # includes tx = ty = inf
|
|
143
|
+
if txy > 1: # == tx**2
|
|
144
|
+
txy = _1_over(txy)
|
|
145
|
+
r = txy + e2m1
|
|
146
|
+
else:
|
|
147
|
+
r = txy * e2m1 + _1_0
|
|
148
|
+
r = _over((txy + _1_0) * fm1, r)
|
|
149
|
+
else:
|
|
150
|
+
if txy > 1:
|
|
151
|
+
tx = _1_over(tx)
|
|
152
|
+
ty = _1_over(ty)
|
|
153
|
+
txy = _1_over(txy)
|
|
154
|
+
t = txy + e2m1
|
|
155
|
+
else:
|
|
156
|
+
t = txy * e2m1 + _1_0
|
|
157
|
+
r = ty - tx
|
|
158
|
+
r = _over(atan2(r * fm1, t), atan2(r, txy + _1_0))
|
|
159
|
+
return r
|
|
160
|
+
|
|
161
|
+
def DRectifying(self, Phi1, Phi2):
|
|
162
|
+
'''I{Divided Difference} of the rectifying wrt the geographic latitude.
|
|
163
|
+
'''
|
|
164
|
+
# Stipulate that Phi1 and Phi2 are in [-90d, 90d]
|
|
165
|
+
x, y = Phi1.toRadians, Phi2.toRadians
|
|
166
|
+
if y == x: # isnear0
|
|
167
|
+
Mu1 = self.Rectifying(Phi1, diff=True)
|
|
168
|
+
tphi1, r = Phi1.tan, Mu1.diff
|
|
169
|
+
if isfinite(tphi1):
|
|
170
|
+
r *= _over(_sc(tphi1), _sc(Mu1.tan))**2
|
|
171
|
+
else: # PYCHOK no cover
|
|
172
|
+
r = _1_over(r)
|
|
173
|
+
elif (x * y) < 0:
|
|
174
|
+
r = _over(self.Rectifying(Phi2).toRadians -
|
|
175
|
+
self.Rectifying(Phi1).toRadians, y - x)
|
|
176
|
+
else:
|
|
177
|
+
r = _over(self.b, self.RectifyingRadius(True))
|
|
178
|
+
r *= self.DE(*map1(self.Parametric, Phi1, Phi2))
|
|
179
|
+
r *= self.DParametric(Phi1, Phi2)
|
|
180
|
+
return r # or INF or NAN
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _DClenshaw(sinp, Zeta1, Zeta2, cs, K):
|
|
184
|
+
'''(INTERNAL) I{Divided Difference} of L{AuxLat._Clenshaw}.
|
|
185
|
+
|
|
186
|
+
@return: C{float} if B{C{sinp}} otherwise a C{Fsum}.
|
|
187
|
+
'''
|
|
188
|
+
s1, c1, r1 = Zeta1._yxr_normalized(False)
|
|
189
|
+
s2, c2, r2 = Zeta2._yxr_normalized(False)
|
|
190
|
+
Delta = r2 - r1
|
|
191
|
+
# Evaluate (Clenshaw(sinp, szeta2, czeta2, cs, K) -
|
|
192
|
+
# Clenshaw(sinp, szeta1, czeta1, cs, K)) / Delta
|
|
193
|
+
# or f = sin if sinp else cos
|
|
194
|
+
# sum(cs[k] * (f((2*k+2) * Zeta2) -
|
|
195
|
+
# f((2*k+2) * Zeta2))) / Delta
|
|
196
|
+
#
|
|
197
|
+
# Delta is EITHER 1, giving the plain difference OR (Zeta2 - Zeta1)
|
|
198
|
+
# in radians, giving the I{Divided Difference}. Other values will
|
|
199
|
+
# produce nonsense.
|
|
200
|
+
#
|
|
201
|
+
# Suffices a and b denote [1,1], [2,1] elements of matrix/vector
|
|
202
|
+
cp = cm = c2 * c1
|
|
203
|
+
t = s2 * s1
|
|
204
|
+
cp -= t # not +
|
|
205
|
+
cm += t # not -
|
|
206
|
+
|
|
207
|
+
sp = s2 * c1
|
|
208
|
+
t = c2 * s1
|
|
209
|
+
smd = ((sin(Delta) / Delta) if Delta != _1_0 else
|
|
210
|
+
(sp - t)) if Delta else _1_0
|
|
211
|
+
sp += t
|
|
212
|
+
|
|
213
|
+
xa = cp * cm * _2_0
|
|
214
|
+
xb = sp * smd * _N_2_0
|
|
215
|
+
xD = xb * Delta**2
|
|
216
|
+
|
|
217
|
+
if isfinite(xD) and isfinite(xb) and isfinite(xa):
|
|
218
|
+
U0a, U1a = Fsum(), Fsum()
|
|
219
|
+
U0b, U1b = Fsum(), Fsum()
|
|
220
|
+
for k in _reverange(K): # assert len(cs) == K
|
|
221
|
+
# t = x . U0 - U1 + cs[k] * I
|
|
222
|
+
U1a -= U0a * xa + U0b * xD + cs[k]
|
|
223
|
+
U1b -= U0a * xb + U0b * xa
|
|
224
|
+
U1a, U0a = U0a, -U1a
|
|
225
|
+
U1b, U0b = U0b, -U1b
|
|
226
|
+
# F0a = (sp if sinp else cp) * cm
|
|
227
|
+
# F0b = (cp if sinp else -sp) * smd
|
|
228
|
+
# Fm1a = 0 if sinp else 1 # Fm1b = 0
|
|
229
|
+
# return (U0b * F0a + U0a * F0b - U1b * Fm1a) * 2
|
|
230
|
+
if sinp:
|
|
231
|
+
U1b = _0_0
|
|
232
|
+
else:
|
|
233
|
+
sp, cp = cp, -sp
|
|
234
|
+
U0b *= sp * cm
|
|
235
|
+
U0a *= cp * smd
|
|
236
|
+
U0a += U0b
|
|
237
|
+
U0a = _Dm(U0a, U1b, _2_0)
|
|
238
|
+
r = float(U0a) if sinp else U0a # Fsum
|
|
239
|
+
else:
|
|
240
|
+
r = _naninf(xD, xb, xa)
|
|
241
|
+
return r
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _Dsin(x, y): # see also .rhumb.ekx._Dsin
|
|
245
|
+
r = cos((x + y) * _0_5)
|
|
246
|
+
d = (x - y) * _0_5
|
|
247
|
+
if d:
|
|
248
|
+
r *= sin(d) / d
|
|
249
|
+
return r
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _Dsn(x, y):
|
|
253
|
+
# (sn(y) - sn(x)) / (y - x)
|
|
254
|
+
if x != y:
|
|
255
|
+
snx, sny = _sn(x), _sn(y)
|
|
256
|
+
if (x * y) > 0:
|
|
257
|
+
scx, scy = _sc(x), _sc(y)
|
|
258
|
+
r = _over((snx / scy) + (sny / scx),
|
|
259
|
+
(snx + sny) * scy * scx)
|
|
260
|
+
else:
|
|
261
|
+
r = (sny - snx) / (y - x)
|
|
262
|
+
elif x:
|
|
263
|
+
r = _1_over(_sc(x) * (x**2 + _1_0)) # == 1 / sqrt3(x**2 + 1)
|
|
264
|
+
else:
|
|
265
|
+
r = _1_0
|
|
266
|
+
return r
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _sxk2y(sx, sy, k2):
|
|
270
|
+
# .DE helper
|
|
271
|
+
sy *= sy * k2
|
|
272
|
+
if sy:
|
|
273
|
+
try:
|
|
274
|
+
sx *= sqrt(_1_0 - sy)
|
|
275
|
+
except ValueError: # domain error
|
|
276
|
+
sx = NAN
|
|
277
|
+
return sx
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
__all__ += _ALL_DOCS(AuxDLat)
|
|
281
|
+
|
|
282
|
+
# **) MIT License
|
|
283
|
+
#
|
|
284
|
+
# Copyright (C) 2023-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
285
|
+
#
|
|
286
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
287
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
288
|
+
# to deal in the Software without restriction, including without limitation
|
|
289
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
290
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
291
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
292
|
+
#
|
|
293
|
+
# The above copyright notice and this permission notice shall be included
|
|
294
|
+
# in all copies or substantial portions of the Software.
|
|
295
|
+
#
|
|
296
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
297
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
298
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
299
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
300
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
301
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
302
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Discrete Sine Transforms (AuxDST) in Python, transcoded from I{Karney}'s C++ class
|
|
5
|
+
U{DST<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1DST.html>}
|
|
6
|
+
in I{GeographicLib version 2.2+}.
|
|
7
|
+
|
|
8
|
+
Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2022-2023) and licensed
|
|
9
|
+
under the MIT/X11 License. For more information, see the U{GeographicLib
|
|
10
|
+
<https://GeographicLib.SourceForge.io>} documentation.
|
|
11
|
+
|
|
12
|
+
@note: Class L{AuxDST} requires U{numpy<https://PyPI.org/project/numpy>} to be
|
|
13
|
+
installed, version 1.16 or newer.
|
|
14
|
+
'''
|
|
15
|
+
# make sure int/int division yields float quotient, see .basics
|
|
16
|
+
from __future__ import division as _; del _ # PYCHOK semicolon
|
|
17
|
+
|
|
18
|
+
from pygeodesy.auxilats.auxily import _Dm
|
|
19
|
+
from pygeodesy.basics import isodd, neg, _reverange, _xnumpy
|
|
20
|
+
from pygeodesy.constants import PI_2, PI_4, isfinite, _0_0, _0_5, _naninf
|
|
21
|
+
from pygeodesy.fsums import Fsum, property_RO
|
|
22
|
+
from pygeodesy.karney import _2cos2x, _ALL_DOCS
|
|
23
|
+
# from pygeodesy.lazily import _ALL_DOCS # from .karney
|
|
24
|
+
# from pygeodesy.props import property_RO # from .fsums
|
|
25
|
+
|
|
26
|
+
__all__ = ()
|
|
27
|
+
__version__ = '23.12.02'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AuxDST(object):
|
|
31
|
+
'''Discrete Sine Transforms (DST) for I{Auxiliary} latitudes.
|
|
32
|
+
|
|
33
|
+
@see: I{Karney}'s C++ class U{DST
|
|
34
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1DST.html>}.
|
|
35
|
+
'''
|
|
36
|
+
_N = 0
|
|
37
|
+
|
|
38
|
+
def __init__(self, N):
|
|
39
|
+
'''New L{AuxDST} instance.
|
|
40
|
+
|
|
41
|
+
@arg N: Size, number of points (C{int}).
|
|
42
|
+
'''
|
|
43
|
+
if N > 0:
|
|
44
|
+
self._N = int(N)
|
|
45
|
+
# kissfft(N, False) # size, inverse
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def evaluate(sinx, cosx, F, *N):
|
|
49
|
+
'''Evaluate the Fourier sum given the sine and cosine of the angle,
|
|
50
|
+
using precision I{Clenshaw} summation.
|
|
51
|
+
|
|
52
|
+
@arg sinx: The sin(I{sigma}) (C{float}).
|
|
53
|
+
@arg cosx: The cos(I{sigma}) (C{float}).
|
|
54
|
+
@arg F: The Fourier coefficients (C{float}[]).
|
|
55
|
+
@arg N: Optional, (smaller) number of terms to evaluate (C{int}).
|
|
56
|
+
|
|
57
|
+
@return: Precison I{Clenshaw} sum (C{float}).
|
|
58
|
+
|
|
59
|
+
@see: Methods C{AuxDST.integral} and C{AuxDST.integral2}.
|
|
60
|
+
'''
|
|
61
|
+
a = -_2cos2x(cosx, sinx)
|
|
62
|
+
if isfinite(a):
|
|
63
|
+
Y0, Y1 = Fsum(), Fsum()
|
|
64
|
+
n = _len_N(F, *N)
|
|
65
|
+
Fn = list(F[:n])
|
|
66
|
+
_F = Fn.pop
|
|
67
|
+
if isodd(n):
|
|
68
|
+
Y0 -= _F()
|
|
69
|
+
while Fn: # Y0, Y1 negated
|
|
70
|
+
Y1 -= Y0 * a + _F()
|
|
71
|
+
Y0 -= Y1 * a + _F()
|
|
72
|
+
r = float(_Dm(-Y0, Y1, sinx))
|
|
73
|
+
else:
|
|
74
|
+
r = _naninf(-a)
|
|
75
|
+
return r
|
|
76
|
+
|
|
77
|
+
@property_RO
|
|
78
|
+
def _fft_numpy(self):
|
|
79
|
+
'''(INTERNAL) Get the C{numpy.fft} module, I{once}.
|
|
80
|
+
'''
|
|
81
|
+
AuxDST._fft_numpy = fft = _xnumpy(AuxDST, 1, 16).fft # overwrite property_RO
|
|
82
|
+
return fft
|
|
83
|
+
|
|
84
|
+
def _fft_real(self, data):
|
|
85
|
+
'''(INTERNAL) NumPy's I{kissfft}-like C{transform_real} function,
|
|
86
|
+
taking C{float}[:N] B{C{data}} and returning C{complex}[:N*2].
|
|
87
|
+
'''
|
|
88
|
+
# <https://GitHub.com/mborgerding/kissfft/blob/master/test/testkiss.py>
|
|
89
|
+
return self._fft_numpy.rfftn(data)
|
|
90
|
+
|
|
91
|
+
def _ffts(self, data, cIV):
|
|
92
|
+
'''(INTERNAL) Compute the DST-III or DST-IV FFTransforms.
|
|
93
|
+
|
|
94
|
+
@arg data: Elements DST-III[0:N+1] or DST-IV[0:N] (C{float}[])
|
|
95
|
+
with DST_III[0] = 0.
|
|
96
|
+
@arg cIV: If C{True} DST-IV, otherwise DST-III.
|
|
97
|
+
|
|
98
|
+
@return: FFTransforms (C{float}[0:N]).
|
|
99
|
+
'''
|
|
100
|
+
T, N = (), self.N
|
|
101
|
+
if N > 0:
|
|
102
|
+
N2 = N * 2
|
|
103
|
+
d = tuple(data)
|
|
104
|
+
# assert len(d) == N + (0 if cIV else 1)
|
|
105
|
+
|
|
106
|
+
if cIV: # DST-IV
|
|
107
|
+
from cmath import exp as _cexp
|
|
108
|
+
|
|
109
|
+
def _cF(c, j, r=-PI_4 / N):
|
|
110
|
+
return c * _cexp(complex(0, r * j))
|
|
111
|
+
|
|
112
|
+
i = 0
|
|
113
|
+
else: # DST-III
|
|
114
|
+
i = 1
|
|
115
|
+
# assert d[0] == _0_0
|
|
116
|
+
|
|
117
|
+
def _cF(c, *unused): # PYCHOK redef
|
|
118
|
+
return c
|
|
119
|
+
|
|
120
|
+
d += tuple(reversed(d[i:N])) # i == len(d) - N
|
|
121
|
+
d += tuple(map(neg, d[:N2]))
|
|
122
|
+
c = self._fft_real(d) # complex[0:N*2]
|
|
123
|
+
n2 = float(-N2)
|
|
124
|
+
T = tuple(_cF(c[j], j).imag / n2 for j in range(1, N2, 2))
|
|
125
|
+
return T
|
|
126
|
+
|
|
127
|
+
def _ffts2(self, data, F):
|
|
128
|
+
'''(INTERNAL) Doubled FFTransforms.
|
|
129
|
+
|
|
130
|
+
@arg data: Grid centers (C{float}[N]).
|
|
131
|
+
@arg F: The transforms (C{float}[N])
|
|
132
|
+
|
|
133
|
+
@return: Doubled FFTransforms (C{float}[N*2]).
|
|
134
|
+
'''
|
|
135
|
+
__2 = _0_5 # N = self._N
|
|
136
|
+
# copy DST-IV order N transform to D[0:N]
|
|
137
|
+
D = self._ffts(data, True)
|
|
138
|
+
# assert len(D) == N and len(F) >= N
|
|
139
|
+
# (DST-IV order N - DST-III order N) / 2
|
|
140
|
+
M = tuple((d - f) * __2 for d, f in zip(D, F)) # strict=False
|
|
141
|
+
# (DST-IV order N + DST-III order N) / 2
|
|
142
|
+
P = tuple((d + f) * __2 for d, f in zip(D, F)) # strict=False
|
|
143
|
+
# assert len(M) == len(P) == self._N
|
|
144
|
+
return P + tuple(reversed(M))
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def integral(sinx, cosx, F, *N):
|
|
148
|
+
'''Evaluate the integral of Fourier sum given the sine and
|
|
149
|
+
cosine of the angle, using precision I{Clenshaw} summation.
|
|
150
|
+
|
|
151
|
+
@arg sinx: The sin(I{sigma}) (C{float}).
|
|
152
|
+
@arg cosx: The cos(I{sigma}) (C{float}).
|
|
153
|
+
@arg F: The Fourier coefficients (C{float}[]).
|
|
154
|
+
@arg N: Optional, C{len(B{F})} or a (smaller) number of
|
|
155
|
+
terms to evaluate (C{int}).
|
|
156
|
+
|
|
157
|
+
@return: Precison I{Clenshaw} intergral (C{float}).
|
|
158
|
+
|
|
159
|
+
@see: Methods C{AuxDST.evaluate} and C{AuxDST.integral2}.
|
|
160
|
+
'''
|
|
161
|
+
a = _2cos2x(cosx - sinx, cosx + sinx)
|
|
162
|
+
if isfinite(a):
|
|
163
|
+
Y0, Y1 = Fsum(), Fsum()
|
|
164
|
+
for r in _reverscaled(F, *N):
|
|
165
|
+
Y1 -= Y0 * a + r
|
|
166
|
+
Y1, Y0 = Y0, -Y1
|
|
167
|
+
r = float(_Dm(Y1, Y0, cosx))
|
|
168
|
+
else:
|
|
169
|
+
r = _naninf(a)
|
|
170
|
+
return r
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def integral2(sinx, cosx, siny, cosy, F, *N): # PYCHOK no cover
|
|
174
|
+
'''Compute the definite integral of Fourier sum given the
|
|
175
|
+
sine and cosine of the angles at the end points, using
|
|
176
|
+
precision I{Clenshaw} summation.
|
|
177
|
+
|
|
178
|
+
@arg sinx: The sin(I{sigma1}) (C{float}).
|
|
179
|
+
@arg cosx: The cos(I{sigma1}) (C{float}).
|
|
180
|
+
@arg siny: The sin(I{sigma2}) (C{float}).
|
|
181
|
+
@arg cosy: The cos(I{sigma2}) (C{float}).
|
|
182
|
+
@arg F: The Fourier coefficients (C{float}[]).
|
|
183
|
+
@arg N: Optional, C{len(B{F})} or a (smaller) number of
|
|
184
|
+
terms to evaluate (C{int}).
|
|
185
|
+
|
|
186
|
+
@return: Precison I{Clenshaw} integral (C{float}).
|
|
187
|
+
|
|
188
|
+
@see: Methods C{AuxDST.evaluate} and C{AuxDST.integral}.
|
|
189
|
+
'''
|
|
190
|
+
# 2 * cos(y - x) * cos(y + x) -> 2 * cos(2 * x)
|
|
191
|
+
c = _2cos2x(cosy * cosx, siny * sinx)
|
|
192
|
+
# -2 * sin(y - x) * sin(y + x) -> 0
|
|
193
|
+
s = -_2cos2x(siny * cosx, cosy * sinx)
|
|
194
|
+
if isfinite(c) and isfinite(s):
|
|
195
|
+
Y0, Y1 = Fsum(), Fsum()
|
|
196
|
+
Z0, Z1 = Fsum(), Fsum()
|
|
197
|
+
for r in _reverscaled(F, *N):
|
|
198
|
+
Y1 -= Y0 * c + Z0 * s + r
|
|
199
|
+
Z1 -= Y0 * s + Z0 * c
|
|
200
|
+
Y1, Y0 = Y0, -Y1
|
|
201
|
+
Z1, Z0 = Z0, -Z1
|
|
202
|
+
r = float(_Dm(Y1, Y0, cosy - cosx) +
|
|
203
|
+
_Dm(Z1, Z0, cosy + cosx))
|
|
204
|
+
else:
|
|
205
|
+
r = _naninf(c, s)
|
|
206
|
+
return r
|
|
207
|
+
|
|
208
|
+
@property_RO
|
|
209
|
+
def N(self):
|
|
210
|
+
'''Get this DST's size, number of points (C{int}).
|
|
211
|
+
'''
|
|
212
|
+
return self._N
|
|
213
|
+
|
|
214
|
+
def refine(self, f, F, *sentinel):
|
|
215
|
+
'''Refine the Fourier series by doubling the sampled points.
|
|
216
|
+
|
|
217
|
+
@arg f: Single-argument callable (C{B{f}(sigma)}).
|
|
218
|
+
@arg F: Initial Fourier series coefficients (C{float}[:N]).
|
|
219
|
+
@arg sentinel: Optional coefficient(s) to append (C{float}(s)).
|
|
220
|
+
|
|
221
|
+
@return: Fourier series coefficients (C{float}[:N*2]).
|
|
222
|
+
|
|
223
|
+
@note: Any initial C{B{F}[N:]} sentinel coefficients are ignored.
|
|
224
|
+
'''
|
|
225
|
+
def _data(_f, N): # [:N]
|
|
226
|
+
if N > 0:
|
|
227
|
+
r = PI_4 / N
|
|
228
|
+
for j in range(1, N*2, 2):
|
|
229
|
+
yield _f(r * j)
|
|
230
|
+
|
|
231
|
+
# F = F[:self.N] handled by zip strict=False in ._ffts2 above
|
|
232
|
+
return self._ffts2(_data(f, self.N), F) + sentinel
|
|
233
|
+
|
|
234
|
+
def reset(self, N):
|
|
235
|
+
'''Reset this DST.
|
|
236
|
+
|
|
237
|
+
@arg N: Size, number of points (C{int}).
|
|
238
|
+
|
|
239
|
+
@return: The new size (C{int}, non-negative).
|
|
240
|
+
'''
|
|
241
|
+
self._N = N = max(0, N)
|
|
242
|
+
# kissfft.assign(N*2, False) # "reset" size, inverse
|
|
243
|
+
return N
|
|
244
|
+
|
|
245
|
+
def transform(self, f):
|
|
246
|
+
'''Determine C{[N + 1]} terms in the Fourier series.
|
|
247
|
+
|
|
248
|
+
@arg f: Single-argument callable (C{B{f}(sigma)}).
|
|
249
|
+
|
|
250
|
+
@return: Fourier series coefficients (C{float}[:N+1],
|
|
251
|
+
leading 0).
|
|
252
|
+
'''
|
|
253
|
+
def _data(_f, N): # [:N + 1]
|
|
254
|
+
yield _0_0 # data[0] = 0
|
|
255
|
+
if N > 0:
|
|
256
|
+
r = PI_2 / N
|
|
257
|
+
for i in range(1, N + 1):
|
|
258
|
+
yield _f(r * i)
|
|
259
|
+
|
|
260
|
+
return self._ffts(_data(f, self.N), False)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _len_N(F, *N):
|
|
264
|
+
# Adjusted C{len(B{F})}.
|
|
265
|
+
return min(len(F), *N) if N else len(F)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _reverscaled(F, *N):
|
|
269
|
+
# Yield F[:N], reversed and scaled
|
|
270
|
+
for n in _reverange(_len_N(F, *N)):
|
|
271
|
+
yield F[n] / float(n * 2 + 1)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
__all__ += _ALL_DOCS(AuxDST)
|
|
275
|
+
|
|
276
|
+
# **) MIT License
|
|
277
|
+
#
|
|
278
|
+
# Copyright (C) 2023-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
279
|
+
#
|
|
280
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
281
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
282
|
+
# to deal in the Software without restriction, including without limitation
|
|
283
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
284
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
285
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
286
|
+
#
|
|
287
|
+
# The above copyright notice and this permission notice shall be included
|
|
288
|
+
# in all copies or substantial portions of the Software.
|
|
289
|
+
#
|
|
290
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
291
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
292
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
293
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
294
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
295
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
296
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|