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
pygeodesy/nvectorBase.py
ADDED
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''(INTERNAL) Private elliposiodal and spherical C{Nvector} base classes
|
|
5
|
+
L{LatLonNvectorBase} and L{NvectorBase} and function L{sumOf}.
|
|
6
|
+
|
|
7
|
+
Pure Python implementation of C{n-vector}-based geodesy tools for ellipsoidal
|
|
8
|
+
earth models, transcoded from JavaScript originals by I{(C) Chris Veness 2005-2016}
|
|
9
|
+
and published under the same MIT Licence**, see U{Vector-based geodesy
|
|
10
|
+
<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}.
|
|
11
|
+
'''
|
|
12
|
+
|
|
13
|
+
# from pygeodesy.basics import map1 # from .namedTuples
|
|
14
|
+
from pygeodesy.constants import EPS, EPS1, EPS_2, R_M, _2_0, _N_2_0
|
|
15
|
+
# from pygeodesy.datums import _spherical_datum # from .formy
|
|
16
|
+
from pygeodesy.errors import IntersectionError, _ValueError, VectorError, \
|
|
17
|
+
_xkwds, _xkwds_pop2
|
|
18
|
+
from pygeodesy.fmath import fdot, fidw, hypot_ # PYCHOK fdot shared
|
|
19
|
+
from pygeodesy.fsums import Fsum, fsumf_
|
|
20
|
+
from pygeodesy.formy import _isequalTo, n_xyz2latlon, n_xyz2philam, \
|
|
21
|
+
_spherical_datum
|
|
22
|
+
from pygeodesy.interns import NN, _1_, _2_, _3_, _bearing_, _coincident_, \
|
|
23
|
+
_COMMASPACE_, _distance_, _h_, _insufficient_, \
|
|
24
|
+
_intersection_, _no_, _NorthPole_, _point_, \
|
|
25
|
+
_pole_, _SPACE_, _SouthPole_, _under
|
|
26
|
+
from pygeodesy.latlonBase import LatLonBase, _ALL_DOCS, _ALL_LAZY, _MODS
|
|
27
|
+
# from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS # from .latlonBase
|
|
28
|
+
from pygeodesy.named import notImplemented, _xother3
|
|
29
|
+
from pygeodesy.namedTuples import Trilaterate5Tuple, Vector3Tuple, \
|
|
30
|
+
Vector4Tuple, map1
|
|
31
|
+
from pygeodesy.props import deprecated_method, Property_RO, property_doc_, \
|
|
32
|
+
property_RO, _update_all
|
|
33
|
+
from pygeodesy.streprs import Fmt, hstr, unstr, _xattrs
|
|
34
|
+
from pygeodesy.units import Bearing, Height, Radius_, Scalar
|
|
35
|
+
from pygeodesy.utily import sincos2d, _unrollon, _unrollon3
|
|
36
|
+
from pygeodesy.vector3d import Vector3d, _xyzhdn3
|
|
37
|
+
|
|
38
|
+
from math import fabs, sqrt
|
|
39
|
+
|
|
40
|
+
__all__ = _ALL_LAZY.nvectorBase
|
|
41
|
+
__version__ = '24.02.18'
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class NvectorBase(Vector3d): # XXX kept private
|
|
45
|
+
'''Base class for ellipsoidal and spherical C{Nvector}s.
|
|
46
|
+
'''
|
|
47
|
+
_datum = None # L{Datum}, overriden
|
|
48
|
+
_h = Height(h=0) # height (C{meter})
|
|
49
|
+
_H = NN # height prefix (C{str}), '↑' in JS version
|
|
50
|
+
|
|
51
|
+
def __init__(self, x_xyz, y=None, z=None, h=0, ll=None, datum=None, name=NN):
|
|
52
|
+
'''New n-vector normal to the earth's surface.
|
|
53
|
+
|
|
54
|
+
@arg x_xyz: X component of vector (C{scalar}) or (3-D) vector
|
|
55
|
+
(C{Nvector}, L{Vector3d}, L{Vector3Tuple} or
|
|
56
|
+
L{Vector4Tuple}).
|
|
57
|
+
@kwarg y: Y component of vector (C{scalar}), ignored if B{C{x_xyz}}
|
|
58
|
+
is not C{scalar}, otherwise same units as B{C{x_xyz}}.
|
|
59
|
+
@kwarg z: Z component of vector (C{scalar}), ignored if B{C{x_xyz}}
|
|
60
|
+
is not C{scalar}, otherwise same units as B{C{x_xyz}}.
|
|
61
|
+
@kwarg h: Optional height above surface (C{meter}).
|
|
62
|
+
@kwarg ll: Optional, original latlon (C{LatLon}).
|
|
63
|
+
@kwarg datum: Optional, I{pass-thru} datum (L{Datum}).
|
|
64
|
+
@kwarg name: Optional name (C{str}).
|
|
65
|
+
|
|
66
|
+
@raise TypeError: Non-scalar B{C{x}}, B{C{y}} or B{C{z}}
|
|
67
|
+
coordinate or B{C{x}} not an C{Nvector},
|
|
68
|
+
L{Vector3Tuple} or L{Vector4Tuple} or
|
|
69
|
+
invalid B{C{datum}}.
|
|
70
|
+
'''
|
|
71
|
+
h, d, n = _xyzhdn3(x_xyz, h, datum, ll)
|
|
72
|
+
Vector3d.__init__(self, x_xyz, y=y, z=z, ll=ll, name=name or n)
|
|
73
|
+
if h:
|
|
74
|
+
self.h = h
|
|
75
|
+
if d is not None:
|
|
76
|
+
self._datum = _spherical_datum(d, name=self.name) # pass-thru
|
|
77
|
+
|
|
78
|
+
@Property_RO
|
|
79
|
+
def datum(self):
|
|
80
|
+
'''Get the I{pass-thru} datum (C{Datum}) or C{None}.
|
|
81
|
+
'''
|
|
82
|
+
return self._datum
|
|
83
|
+
|
|
84
|
+
@property_RO
|
|
85
|
+
def Ecef(self):
|
|
86
|
+
'''Get the ECEF I{class} (L{EcefKarney}), I{once}.
|
|
87
|
+
'''
|
|
88
|
+
NvectorBase.Ecef = E = _MODS.ecef.EcefKarney # overwrite property_RO
|
|
89
|
+
return E
|
|
90
|
+
|
|
91
|
+
@property_RO
|
|
92
|
+
def ellipsoidalNvector(self):
|
|
93
|
+
'''Get the C{Nvector type} iff ellipsoidal, overloaded in L{pygeodesy.ellipsoidalNvector.Nvector}.
|
|
94
|
+
'''
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
@property_doc_(''' the height above surface (C{meter}).''')
|
|
98
|
+
def h(self):
|
|
99
|
+
'''Get the height above surface (C{meter}).
|
|
100
|
+
'''
|
|
101
|
+
return self._h
|
|
102
|
+
|
|
103
|
+
@h.setter # PYCHOK setter!
|
|
104
|
+
def h(self, h):
|
|
105
|
+
'''Set the height above surface (C{meter}).
|
|
106
|
+
|
|
107
|
+
@raise TypeError: If B{C{h}} invalid.
|
|
108
|
+
|
|
109
|
+
@raise VectorError: If B{C{h}} invalid.
|
|
110
|
+
'''
|
|
111
|
+
h = Height(h=h, Error=VectorError)
|
|
112
|
+
if self._h != h:
|
|
113
|
+
_update_all(self)
|
|
114
|
+
self._h = h
|
|
115
|
+
|
|
116
|
+
@property_doc_(''' the height prefix (C{str}).''')
|
|
117
|
+
def H(self):
|
|
118
|
+
'''Get the height prefix (C{str}).
|
|
119
|
+
'''
|
|
120
|
+
return self._H
|
|
121
|
+
|
|
122
|
+
@H.setter # PYCHOK setter!
|
|
123
|
+
def H(self, H):
|
|
124
|
+
'''Set the height prefix (C{str}).
|
|
125
|
+
'''
|
|
126
|
+
self._H = str(H) if H else NN
|
|
127
|
+
|
|
128
|
+
def hStr(self, prec=-2, m=NN):
|
|
129
|
+
'''Return a string for the height B{C{h}}.
|
|
130
|
+
|
|
131
|
+
@kwarg prec: Number of (decimal) digits, unstripped (C{int}).
|
|
132
|
+
@kwarg m: Optional unit of the height (C{str}).
|
|
133
|
+
|
|
134
|
+
@see: Function L{pygeodesy.hstr}.
|
|
135
|
+
'''
|
|
136
|
+
return NN(self.H, hstr(self.h, prec=prec, m=m))
|
|
137
|
+
|
|
138
|
+
@Property_RO
|
|
139
|
+
def isEllipsoidal(self):
|
|
140
|
+
'''Check whether this n-vector is ellipsoidal (C{bool} or C{None} if unknown).
|
|
141
|
+
'''
|
|
142
|
+
return self.datum.isEllipsoidal if self.datum else None
|
|
143
|
+
|
|
144
|
+
@Property_RO
|
|
145
|
+
def isSpherical(self):
|
|
146
|
+
'''Check whether this n-vector is spherical (C{bool} or C{None} if unknown).
|
|
147
|
+
'''
|
|
148
|
+
return self.datum.isSpherical if self.datum else None
|
|
149
|
+
|
|
150
|
+
@Property_RO
|
|
151
|
+
def lam(self):
|
|
152
|
+
'''Get the (geodetic) longitude in C{radians} (C{float}).
|
|
153
|
+
'''
|
|
154
|
+
return self.philam.lam
|
|
155
|
+
|
|
156
|
+
@Property_RO
|
|
157
|
+
def lat(self):
|
|
158
|
+
'''Get the (geodetic) latitude in C{degrees} (C{float}).
|
|
159
|
+
'''
|
|
160
|
+
return self.latlon.lat
|
|
161
|
+
|
|
162
|
+
@Property_RO
|
|
163
|
+
def latlon(self):
|
|
164
|
+
'''Get the (geodetic) lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
|
|
165
|
+
'''
|
|
166
|
+
return n_xyz2latlon(self.x, self.y, self.z, name=self.name)
|
|
167
|
+
|
|
168
|
+
@Property_RO
|
|
169
|
+
def latlonheight(self):
|
|
170
|
+
'''Get the (geodetic) lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
|
|
171
|
+
'''
|
|
172
|
+
return self.latlon.to3Tuple(self.h)
|
|
173
|
+
|
|
174
|
+
@Property_RO
|
|
175
|
+
def latlonheightdatum(self):
|
|
176
|
+
'''Get the lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}).
|
|
177
|
+
'''
|
|
178
|
+
return self.latlonheight.to4Tuple(self.datum)
|
|
179
|
+
|
|
180
|
+
@Property_RO
|
|
181
|
+
def lon(self):
|
|
182
|
+
'''Get the (geodetic) longitude in C{degrees} (C{float}).
|
|
183
|
+
'''
|
|
184
|
+
return self.latlon.lon
|
|
185
|
+
|
|
186
|
+
@Property_RO
|
|
187
|
+
def phi(self):
|
|
188
|
+
'''Get the (geodetic) latitude in C{radians} (C{float}).
|
|
189
|
+
'''
|
|
190
|
+
return self.philam.phi
|
|
191
|
+
|
|
192
|
+
@Property_RO
|
|
193
|
+
def philam(self):
|
|
194
|
+
'''Get the (geodetic) lat-, longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
|
|
195
|
+
'''
|
|
196
|
+
return n_xyz2philam(self.x, self.y, self.z, name=self.name)
|
|
197
|
+
|
|
198
|
+
@Property_RO
|
|
199
|
+
def philamheight(self):
|
|
200
|
+
'''Get the (geodetic) lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
|
|
201
|
+
'''
|
|
202
|
+
return self.philam.to3Tuple(self.h)
|
|
203
|
+
|
|
204
|
+
@Property_RO
|
|
205
|
+
def philamheightdatum(self):
|
|
206
|
+
'''Get the lat-, longitude in C{radians} with height and datum (L{PhiLam4Tuple}C{(phi, lam, height, datum)}).
|
|
207
|
+
'''
|
|
208
|
+
return self.philamheight.to4Tuple(self.datum)
|
|
209
|
+
|
|
210
|
+
@property_RO
|
|
211
|
+
def sphericalNvector(self):
|
|
212
|
+
'''Get the C{Nvector type} iff spherical, overloaded in L{pygeodesy.sphericalNvector.Nvector}.
|
|
213
|
+
'''
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
@deprecated_method
|
|
217
|
+
def to2ab(self): # PYCHOK no cover
|
|
218
|
+
'''DEPRECATED, use property L{philam}.
|
|
219
|
+
|
|
220
|
+
@return: A L{PhiLam2Tuple}C{(phi, lam)}.
|
|
221
|
+
'''
|
|
222
|
+
return self.philam
|
|
223
|
+
|
|
224
|
+
@deprecated_method
|
|
225
|
+
def to3abh(self, height=None): # PYCHOK no cover
|
|
226
|
+
'''DEPRECATED, use property L{philamheight} or C{philam.to3Tuple(B{height})}.
|
|
227
|
+
|
|
228
|
+
@kwarg height: Optional height, overriding this
|
|
229
|
+
n-vector's height (C{meter}).
|
|
230
|
+
|
|
231
|
+
@return: A L{PhiLam3Tuple}C{(phi, lam, height)}.
|
|
232
|
+
|
|
233
|
+
@raise ValueError: Invalid B{C{height}}.
|
|
234
|
+
'''
|
|
235
|
+
return self.philamheight if height in (None, self.h) else \
|
|
236
|
+
self.philam.to3Tuple(height)
|
|
237
|
+
|
|
238
|
+
def toCartesian(self, h=None, Cartesian=None, datum=None, **Cartesian_kwds):
|
|
239
|
+
'''Convert this n-vector to C{Nvector}-based cartesian (ECEF) coordinates.
|
|
240
|
+
|
|
241
|
+
@kwarg h: Optional height, overriding this n-vector's height (C{meter}).
|
|
242
|
+
@kwarg Cartesian: Optional class to return the (ECEF) coordinates
|
|
243
|
+
(C{Cartesian}).
|
|
244
|
+
@kwarg datum: Optional datum (C{Datum}), overriding this datum.
|
|
245
|
+
@kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword
|
|
246
|
+
arguments, ignored if C{B{Cartesian} is None}.
|
|
247
|
+
|
|
248
|
+
@return: The cartesian (ECEF) coordinates (B{C{Cartesian}}) or
|
|
249
|
+
if C{B{Cartesian} is None}, an L{Ecef9Tuple}C{(x, y, z,
|
|
250
|
+
lat, lon, height, C, M, datum)} with C{C} and C{M} if
|
|
251
|
+
available.
|
|
252
|
+
|
|
253
|
+
@raise TypeError: Invalid B{C{Cartesian}} or B{C{Cartesian_kwds}}
|
|
254
|
+
argument.
|
|
255
|
+
|
|
256
|
+
@raise ValueError: Invalid B{C{h}}.
|
|
257
|
+
'''
|
|
258
|
+
D = _spherical_datum(datum or self.datum, name=self.name)
|
|
259
|
+
E = D.ellipsoid
|
|
260
|
+
h = self.h if h is None else Height(h)
|
|
261
|
+
|
|
262
|
+
x, y, z = self.x, self.y, self.z
|
|
263
|
+
# Kenneth Gade eqn 22
|
|
264
|
+
n = E.b / hypot_(x * E.a_b, y * E.a_b, z)
|
|
265
|
+
r = h + n * E.a2_b2
|
|
266
|
+
|
|
267
|
+
x *= r
|
|
268
|
+
y *= r
|
|
269
|
+
z *= h + n
|
|
270
|
+
|
|
271
|
+
if Cartesian is None:
|
|
272
|
+
r = self.Ecef(D).reverse(x, y, z, M=True)
|
|
273
|
+
else:
|
|
274
|
+
kwds = _xkwds(Cartesian_kwds, datum=D) # h=0
|
|
275
|
+
r = Cartesian(x, y, z, **kwds)
|
|
276
|
+
return self._xnamed(r)
|
|
277
|
+
|
|
278
|
+
@deprecated_method
|
|
279
|
+
def to2ll(self): # PYCHOK no cover
|
|
280
|
+
'''DEPRECATED, use property L{latlon}.
|
|
281
|
+
|
|
282
|
+
@return: A L{LatLon2Tuple}C{(lat, lon)}.
|
|
283
|
+
'''
|
|
284
|
+
return self.latlon
|
|
285
|
+
|
|
286
|
+
@deprecated_method
|
|
287
|
+
def to3llh(self, height=None): # PYCHOK no cover
|
|
288
|
+
'''DEPRECATED, use property C{latlonheight} or C{latlon.to3Tuple(B{height})}.
|
|
289
|
+
|
|
290
|
+
@kwarg height: Optional height, overriding this
|
|
291
|
+
n-vector's height (C{meter}).
|
|
292
|
+
|
|
293
|
+
@return: A L{LatLon3Tuple}C{(lat, lon, height)}.
|
|
294
|
+
|
|
295
|
+
@raise ValueError: Invalid B{C{height}}.
|
|
296
|
+
'''
|
|
297
|
+
return self.latlonheight if height in (None, self.h) else \
|
|
298
|
+
self.latlon.to3Tuple(height)
|
|
299
|
+
|
|
300
|
+
def toLatLon(self, height=None, LatLon=None, datum=None, **LatLon_kwds):
|
|
301
|
+
'''Convert this n-vector to an C{Nvector}-based geodetic point.
|
|
302
|
+
|
|
303
|
+
@kwarg height: Optional height, overriding this n-vector's
|
|
304
|
+
height (C{meter}).
|
|
305
|
+
@kwarg LatLon: Optional class to return the geodetic point
|
|
306
|
+
(C{LatLon}) or C{None}.
|
|
307
|
+
@kwarg datum: Optional, spherical datum (C{Datum}).
|
|
308
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
|
|
309
|
+
arguments, ignored if C{B{LatLon} is None}.
|
|
310
|
+
|
|
311
|
+
@return: The geodetic point (C{LatLon}) or if C{B{LatLon} is None},
|
|
312
|
+
an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
|
|
313
|
+
datum)} with C{C} and C{M} if available.
|
|
314
|
+
|
|
315
|
+
@raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_kwds}}
|
|
316
|
+
argument.
|
|
317
|
+
|
|
318
|
+
@raise ValueError: Invalid B{C{height}}.
|
|
319
|
+
'''
|
|
320
|
+
d = _spherical_datum(datum or self.datum, name=self.name)
|
|
321
|
+
h = self.h if height is None else Height(height)
|
|
322
|
+
# use self.Cartesian(Cartesian=None) for better accuracy of the height
|
|
323
|
+
# than self.Ecef(d).forward(self.lat, self.lon, height=h, M=True)
|
|
324
|
+
if LatLon is None:
|
|
325
|
+
r = self.toCartesian(h=h, Cartesian=None, datum=d)
|
|
326
|
+
else:
|
|
327
|
+
kwds = _xkwds(LatLon_kwds, height=h, datum=d)
|
|
328
|
+
r = self._xnamed(LatLon(self.lat, self.lon, **kwds))
|
|
329
|
+
return r
|
|
330
|
+
|
|
331
|
+
def toStr(self, prec=5, fmt=Fmt.PAREN, sep=_COMMASPACE_): # PYCHOK expected
|
|
332
|
+
'''Return a string representation of this n-vector.
|
|
333
|
+
|
|
334
|
+
Height component is only included if non-zero.
|
|
335
|
+
|
|
336
|
+
@kwarg prec: Number of (decimal) digits, unstripped (C{int}).
|
|
337
|
+
@kwarg fmt: Enclosing backets format (C{str}).
|
|
338
|
+
@kwarg sep: Optional separator between components (C{str}).
|
|
339
|
+
|
|
340
|
+
@return: Comma-separated C{"(x, y, z [, h])"} enclosed in
|
|
341
|
+
B{C{fmt}} brackets (C{str}).
|
|
342
|
+
'''
|
|
343
|
+
t = Vector3d.toStr(self, prec=prec, fmt=NN, sep=sep)
|
|
344
|
+
if self.h:
|
|
345
|
+
t = sep.join((t, self.hStr()))
|
|
346
|
+
return (fmt % (t,)) if fmt else t
|
|
347
|
+
|
|
348
|
+
def toVector3d(self, norm=True):
|
|
349
|
+
'''Convert this n-vector to a 3-D vector, I{ignoring height}.
|
|
350
|
+
|
|
351
|
+
@kwarg norm: Normalize the 3-D vector (C{bool}).
|
|
352
|
+
|
|
353
|
+
@return: The (normalized) vector (L{Vector3d}).
|
|
354
|
+
'''
|
|
355
|
+
v = Vector3d.unit(self) if norm else self
|
|
356
|
+
return Vector3d(v.x, v.y, v.z, name=self.name)
|
|
357
|
+
|
|
358
|
+
@deprecated_method
|
|
359
|
+
def to4xyzh(self, h=None): # PYCHOK no cover
|
|
360
|
+
'''DEPRECATED, use property L{xyzh} or C{xyz.to4Tuple(B{h})}.'''
|
|
361
|
+
return self.xyzh if h in (None, self.h) else Vector4Tuple(
|
|
362
|
+
self.x, self.y, self.z, h, name=self.name)
|
|
363
|
+
|
|
364
|
+
def unit(self, ll=None):
|
|
365
|
+
'''Normalize this n-vector to unit length.
|
|
366
|
+
|
|
367
|
+
@kwarg ll: Optional, original latlon (C{LatLon}).
|
|
368
|
+
|
|
369
|
+
@return: Normalized vector (C{Nvector}).
|
|
370
|
+
'''
|
|
371
|
+
return _xattrs(Vector3d.unit(self, ll=ll), _under(_h_))
|
|
372
|
+
|
|
373
|
+
@Property_RO
|
|
374
|
+
def xyzh(self):
|
|
375
|
+
'''Get this n-vector's components (L{Vector4Tuple}C{(x, y, z, h)})
|
|
376
|
+
'''
|
|
377
|
+
return self.xyz.to4Tuple(self.h)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
NorthPole = NvectorBase(0, 0, +1, name=_NorthPole_) # North pole (C{Nvector})
|
|
381
|
+
SouthPole = NvectorBase(0, 0, -1, name=_SouthPole_) # South pole (C{Nvector})
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class _N_vector_(NvectorBase):
|
|
385
|
+
'''(INTERNAL) Minimal, low-overhead C{n-vector}.
|
|
386
|
+
'''
|
|
387
|
+
def __init__(self, x, y, z, h=0, name=NN):
|
|
388
|
+
self._x, self._y, self._z = x, y, z
|
|
389
|
+
if h:
|
|
390
|
+
self._h = h
|
|
391
|
+
if name:
|
|
392
|
+
self.name = name
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class LatLonNvectorBase(LatLonBase):
|
|
396
|
+
'''(INTERNAL) Base class for n-vector-based ellipsoidal and
|
|
397
|
+
spherical C{LatLon} classes.
|
|
398
|
+
'''
|
|
399
|
+
|
|
400
|
+
def _update(self, updated, *attrs, **setters): # PYCHOK _Nv=None
|
|
401
|
+
'''(INTERNAL) Zap cached attributes if updated.
|
|
402
|
+
|
|
403
|
+
@see: C{ellipsoidalNvector.LatLon} and C{sphericalNvector.LatLon}
|
|
404
|
+
for the special case of B{C{_Nv}}.
|
|
405
|
+
'''
|
|
406
|
+
if updated:
|
|
407
|
+
_Nv, setters = _xkwds_pop2(setters, _Nv=None)
|
|
408
|
+
if _Nv is not None:
|
|
409
|
+
if _Nv._fromll is not None:
|
|
410
|
+
_Nv._fromll = None
|
|
411
|
+
self._Nv = None
|
|
412
|
+
LatLonBase._update(self, updated, *attrs, **setters)
|
|
413
|
+
|
|
414
|
+
# def distanceTo(self, other, **kwds): # PYCHOK no cover
|
|
415
|
+
# '''I{Must be overloaded}.'''
|
|
416
|
+
# _MODS.named.notOverloaded(self, other, **kwds)
|
|
417
|
+
|
|
418
|
+
def intersections2(self, radius1, other, radius2, **kwds): # PYCHOK expected
|
|
419
|
+
'''B{Not implemented}, throws a C{NotImplementedError} always.'''
|
|
420
|
+
notImplemented(self, radius1, other, radius2, **kwds)
|
|
421
|
+
|
|
422
|
+
def others(self, *other, **name_other_up):
|
|
423
|
+
'''Refined class comparison.
|
|
424
|
+
|
|
425
|
+
@arg other: The other instance (C{LatLonNvectorBase}).
|
|
426
|
+
@kwarg name_other_up: Overriding C{name=other} and C{up=1}
|
|
427
|
+
keyword arguments.
|
|
428
|
+
|
|
429
|
+
@return: The B{C{other}} if compatible.
|
|
430
|
+
|
|
431
|
+
@raise TypeError: Incompatible B{C{other}} C{type}.
|
|
432
|
+
'''
|
|
433
|
+
if other:
|
|
434
|
+
other0 = other[0]
|
|
435
|
+
if isinstance(other0, (self.__class__, LatLonNvectorBase)): # XXX NvectorBase?
|
|
436
|
+
return other0
|
|
437
|
+
|
|
438
|
+
other, name, up = _xother3(self, other, **name_other_up)
|
|
439
|
+
if not isinstance(other, (self.__class__, LatLonNvectorBase)): # XXX NvectorBase?
|
|
440
|
+
LatLonBase.others(self, other, name=name, up=up + 1)
|
|
441
|
+
return other
|
|
442
|
+
|
|
443
|
+
def toNvector(self, **Nvector_and_kwds): # PYCHOK signature
|
|
444
|
+
'''Convert this point to C{Nvector} components, I{including height}.
|
|
445
|
+
|
|
446
|
+
@kwarg Nvector_and_kwds: Optional C{Nvector} class and C{Nvector} keyword arguments,
|
|
447
|
+
Specify C{B{Nvector}=...} to override this C{Nvector} class
|
|
448
|
+
or use C{B{Nvector}=None}.
|
|
449
|
+
|
|
450
|
+
@return: An C{Nvector} or if C{Nvector is None}, a L{Vector4Tuple}C{(x, y, z, h)}.
|
|
451
|
+
|
|
452
|
+
@raise TypeError: Invalid C{Nvector} or other B{C{Nvector_and_kwds}} item.
|
|
453
|
+
'''
|
|
454
|
+
return LatLonBase.toNvector(self, **_xkwds(Nvector_and_kwds, Nvector=NvectorBase))
|
|
455
|
+
|
|
456
|
+
def triangulate(self, bearing1, other, bearing2, height=None, wrap=False): # PYCHOK signature
|
|
457
|
+
'''Locate a point given this, an other point and the (initial) bearing
|
|
458
|
+
from this and the other point.
|
|
459
|
+
|
|
460
|
+
@arg bearing1: Bearing at this point (compass C{degrees360}).
|
|
461
|
+
@arg other: The other point (C{LatLon}).
|
|
462
|
+
@arg bearing2: Bearing at the other point (compass C{degrees360}).
|
|
463
|
+
@kwarg height: Optional height at the triangulated point,
|
|
464
|
+
overriding the mean height (C{meter}).
|
|
465
|
+
@kwarg wrap: If C{True}, use this and the B{C{other}} point
|
|
466
|
+
I{normalized} (C{bool}).
|
|
467
|
+
|
|
468
|
+
@return: Triangulated point (C{LatLon}).
|
|
469
|
+
|
|
470
|
+
@raise TypeError: Invalid B{C{other}} point.
|
|
471
|
+
|
|
472
|
+
@raise Valuerror: Points coincide.
|
|
473
|
+
'''
|
|
474
|
+
return _triangulate(self, bearing1, self.others(other), bearing2,
|
|
475
|
+
height=height, wrap=wrap, LatLon=self.classof)
|
|
476
|
+
|
|
477
|
+
def trilaterate(self, distance1, point2, distance2, point3, distance3,
|
|
478
|
+
radius=R_M, height=None, useZ=False, wrap=False):
|
|
479
|
+
'''Locate a point at given distances from this and two other points.
|
|
480
|
+
|
|
481
|
+
@arg distance1: Distance to this point (C{meter}, same units
|
|
482
|
+
as B{C{radius}}).
|
|
483
|
+
@arg point2: Second reference point (C{LatLon}).
|
|
484
|
+
@arg distance2: Distance to point2 (C{meter}, same units as
|
|
485
|
+
B{C{radius}}).
|
|
486
|
+
@arg point3: Third reference point (C{LatLon}).
|
|
487
|
+
@arg distance3: Distance to point3 (C{meter}, same units as
|
|
488
|
+
B{C{radius}}).
|
|
489
|
+
@kwarg radius: Mean earth radius (C{meter}).
|
|
490
|
+
@kwarg height: Optional height at trilaterated point, overriding
|
|
491
|
+
the mean height (C{meter}, same units as B{C{radius}}).
|
|
492
|
+
@kwarg useZ: Include Z component iff non-NaN, non-zero (C{bool}).
|
|
493
|
+
@kwarg wrap: If C{True}, use this, B{C{point2}} and B{C{point3}}
|
|
494
|
+
I{normalized} (C{bool}).
|
|
495
|
+
|
|
496
|
+
@return: Trilaterated point (C{LatLon}).
|
|
497
|
+
|
|
498
|
+
@raise IntersectionError: No intersection, trilateration failed.
|
|
499
|
+
|
|
500
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
501
|
+
|
|
502
|
+
@raise ValueError: Some B{C{points}} coincide or invalid B{C{distance1}},
|
|
503
|
+
B{C{distance2}}, B{C{distance3}} or B{C{radius}}.
|
|
504
|
+
|
|
505
|
+
@see: U{Trilateration<https://WikiPedia.org/wiki/Trilateration>},
|
|
506
|
+
Veness' JavaScript U{Trilateration<https://www.Movable-Type.co.UK/
|
|
507
|
+
scripts/latlong-vectors.html>} and method C{LatLon.trilaterate5}
|
|
508
|
+
of other, non-C{Nvector LatLon} classes.
|
|
509
|
+
'''
|
|
510
|
+
return _trilaterate(self, distance1, self.others(point2=point2), distance2,
|
|
511
|
+
self.others(point3=point3), distance3,
|
|
512
|
+
radius=radius, height=height, useZ=useZ,
|
|
513
|
+
wrap=wrap, LatLon=self.classof)
|
|
514
|
+
|
|
515
|
+
def trilaterate5(self, distance1, point2, distance2, point3, distance3, # PYCHOK signature
|
|
516
|
+
area=False, eps=EPS1, radius=R_M, wrap=False):
|
|
517
|
+
'''B{Not implemented} for C{B{area}=True} and falls back to method
|
|
518
|
+
C{trilaterate} otherwise.
|
|
519
|
+
|
|
520
|
+
@return: A L{Trilaterate5Tuple}C{(min, minPoint, max, maxPoint, n)}
|
|
521
|
+
with a single trilaterated intersection C{minPoint I{is}
|
|
522
|
+
maxPoint}, C{min I{is} max} the nearest intersection
|
|
523
|
+
margin and count C{n = 1}.
|
|
524
|
+
|
|
525
|
+
@raise NotImplementedError: Keyword argument C{B{area}=True} not
|
|
526
|
+
(yet) supported.
|
|
527
|
+
|
|
528
|
+
@see: Method L{trilaterate} for other and more details.
|
|
529
|
+
'''
|
|
530
|
+
if area:
|
|
531
|
+
notImplemented(self, area=area)
|
|
532
|
+
|
|
533
|
+
t = _trilaterate(self, distance1, self.others(point2=point2), distance2,
|
|
534
|
+
self.others(point3=point3), distance3,
|
|
535
|
+
radius=radius, useZ=True, wrap=wrap,
|
|
536
|
+
LatLon=self.classof)
|
|
537
|
+
# ... and handle B{C{eps}} and C{IntersectionError}
|
|
538
|
+
# like function C{.latlonBase._trilaterate5}
|
|
539
|
+
d = self.distanceTo(t, radius=radius, wrap=wrap) # PYCHOK distanceTo
|
|
540
|
+
d = min(fabs(distance1 - d), fabs(distance2 - d), fabs(distance3 - d))
|
|
541
|
+
if d < eps: # min is max, minPoint is maxPoint
|
|
542
|
+
return Trilaterate5Tuple(d, t, d, t, 1) # n = 1
|
|
543
|
+
t = _SPACE_(_no_(_intersection_), Fmt.PAREN(min.__name__, Fmt.f(d, prec=3)))
|
|
544
|
+
raise IntersectionError(area=area, eps=eps, radius=radius, wrap=wrap, txt=t)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _nsumOf(nvs, h_None, Vector, Vector_kwds): # .sphericalNvector, .vector3d
|
|
548
|
+
'''(INTERNAL) Separated to allow callers to embellish exceptions.
|
|
549
|
+
'''
|
|
550
|
+
X, Y, Z, n = Fsum(), Fsum(), Fsum(), 0
|
|
551
|
+
H = Fsum() if h_None is None else n
|
|
552
|
+
for n, v in enumerate(nvs or ()): # one pass
|
|
553
|
+
X += v.x
|
|
554
|
+
Y += v.y
|
|
555
|
+
Z += v.z
|
|
556
|
+
H += v.h
|
|
557
|
+
if n < 1:
|
|
558
|
+
raise ValueError(_SPACE_(Fmt.PARENSPACED(len=n), _insufficient_))
|
|
559
|
+
|
|
560
|
+
x, y, z = map1(float, X, Y, Z)
|
|
561
|
+
h = H.fover(n) if h_None is None else h_None
|
|
562
|
+
return Vector3Tuple(x, y, z).to4Tuple(h) if Vector is None else \
|
|
563
|
+
Vector(x, y, z, **_xkwds(Vector_kwds, h=h))
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def sumOf(nvectors, Vector=None, h=None, **Vector_kwds):
|
|
567
|
+
'''Return the I{vectorial} sum of two or more n-vectors.
|
|
568
|
+
|
|
569
|
+
@arg nvectors: Vectors to be added (C{Nvector}[]).
|
|
570
|
+
@kwarg Vector: Optional class for the vectorial sum (C{Nvector})
|
|
571
|
+
or C{None}.
|
|
572
|
+
@kwarg h: Optional height, overriding the mean height (C{meter}).
|
|
573
|
+
@kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword
|
|
574
|
+
arguments, ignored if C{B{Vector} is None}.
|
|
575
|
+
|
|
576
|
+
@return: Vectorial sum (B{C{Vector}}) or a L{Vector4Tuple}C{(x, y,
|
|
577
|
+
z, h)} if B{C{Vector}} is C{None}.
|
|
578
|
+
|
|
579
|
+
@raise VectorError: No B{C{nvectors}}.
|
|
580
|
+
'''
|
|
581
|
+
try:
|
|
582
|
+
return _nsumOf(nvectors, h, Vector, Vector_kwds)
|
|
583
|
+
except (TypeError, ValueError) as x:
|
|
584
|
+
raise VectorError(nvectors=nvectors, Vector=Vector, cause=x)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _triangulate(point1, bearing1, point2, bearing2, height=None,
|
|
588
|
+
wrap=False, **LatLon_and_kwds):
|
|
589
|
+
# (INTERNAL) Locate a point given two known points and initial
|
|
590
|
+
# bearings from those points, see C{LatLon.triangulate} above
|
|
591
|
+
|
|
592
|
+
def _gc(p, b, _i_):
|
|
593
|
+
n = p.toNvector()
|
|
594
|
+
de = NorthPole.cross(n, raiser=_pole_).unit() # east vector @ n
|
|
595
|
+
dn = n.cross(de) # north vector @ n
|
|
596
|
+
s, c = sincos2d(Bearing(b, name=_bearing_ + _i_))
|
|
597
|
+
dest = de.times(s)
|
|
598
|
+
dnct = dn.times(c)
|
|
599
|
+
d = dnct.plus(dest) # direction vector @ n
|
|
600
|
+
return n.cross(d) # great circle point + bearing
|
|
601
|
+
|
|
602
|
+
if wrap:
|
|
603
|
+
point2 = _unrollon(point1, point2, wrap=wrap)
|
|
604
|
+
if _isequalTo(point1, point2, eps=EPS):
|
|
605
|
+
raise _ValueError(points=point2, wrap=wrap, txt=_coincident_)
|
|
606
|
+
|
|
607
|
+
gc1 = _gc(point1, bearing1, _1_) # great circle p1 + b1
|
|
608
|
+
gc2 = _gc(point2, bearing2, _2_) # great circle p2 + b2
|
|
609
|
+
|
|
610
|
+
n = gc1.cross(gc2, raiser=_point_) # n-vector of intersection point
|
|
611
|
+
h = point1._havg(point2, h=height)
|
|
612
|
+
kwds = _xkwds(LatLon_and_kwds, height=h)
|
|
613
|
+
return n.toLatLon(**kwds) # Nvector(n.x, n.y, n.z).toLatLon(...)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _trilaterate(point1, distance1, point2, distance2, point3, distance3,
|
|
617
|
+
radius=R_M, height=None, useZ=False,
|
|
618
|
+
wrap=False, **LatLon_and_kwds):
|
|
619
|
+
# (INTERNAL) Locate a point at given distances from
|
|
620
|
+
# three other points, see LatLon.triangulate above
|
|
621
|
+
|
|
622
|
+
def _nr2(p, d, r, _i_, *qs): # .toNvector and angular distance squared
|
|
623
|
+
for q in qs:
|
|
624
|
+
if _isequalTo(p, q, eps=EPS):
|
|
625
|
+
raise _ValueError(points=p, txt=_coincident_)
|
|
626
|
+
return p.toNvector(), (Scalar(d, name=_distance_ + _i_) / r)**2
|
|
627
|
+
|
|
628
|
+
p1, r = point1, Radius_(radius)
|
|
629
|
+
p2, p3, _ = _unrollon3(p1, point2, point3, wrap)
|
|
630
|
+
|
|
631
|
+
n1, r12 = _nr2(p1, distance1, r, _1_)
|
|
632
|
+
n2, r22 = _nr2(p2, distance2, r, _2_, p1)
|
|
633
|
+
n3, r32 = _nr2(p3, distance3, r, _3_, p1, p2)
|
|
634
|
+
|
|
635
|
+
# the following uses x,y coordinate system with origin at n1, x axis n1->n2
|
|
636
|
+
y = n3.minus(n1)
|
|
637
|
+
x = n2.minus(n1)
|
|
638
|
+
z = None
|
|
639
|
+
|
|
640
|
+
d = x.length # distance n1->n2
|
|
641
|
+
if d > EPS_2: # and y.length > EPS_2:
|
|
642
|
+
X = x.unit() # unit vector in x direction n1->n2
|
|
643
|
+
i = X.dot(y) # signed magnitude of x component of n1->n3
|
|
644
|
+
Y = y.minus(X.times(i)).unit() # unit vector in y direction
|
|
645
|
+
j = Y.dot(y) # signed magnitude of y component of n1->n3
|
|
646
|
+
if fabs(j) > EPS_2:
|
|
647
|
+
# courtesy of U{Carlos Freitas<https://GitHub.com/mrJean1/PyGeodesy/issues/33>}
|
|
648
|
+
x = fsumf_(r12, -r22, d**2) / (d * _2_0) # n1->intersection x- and ...
|
|
649
|
+
y = fsumf_(r12, -r32, i**2, j**2, x * i * _N_2_0) / (j * _2_0) # ... y-component
|
|
650
|
+
# courtesy of U{AleixDev<https://GitHub.com/mrJean1/PyGeodesy/issues/43>}
|
|
651
|
+
z = fsumf_(max(r12, r22, r32), -(x**2), -(y**2)) # XXX not just r12!
|
|
652
|
+
if z > EPS:
|
|
653
|
+
n = n1.plus(X.times(x)).plus(Y.times(y))
|
|
654
|
+
if useZ: # include Z component
|
|
655
|
+
Z = X.cross(Y) # unit vector perpendicular to plane
|
|
656
|
+
n = n.plus(Z.times(sqrt(z)))
|
|
657
|
+
if height is None:
|
|
658
|
+
h = fidw((point1.height, point2.height, point3.height),
|
|
659
|
+
map1(fabs, distance1, distance2, distance3))
|
|
660
|
+
else:
|
|
661
|
+
h = Height(height)
|
|
662
|
+
kwds = _xkwds(LatLon_and_kwds, height=h)
|
|
663
|
+
return n.toLatLon(**kwds) # Nvector(n.x, n.y, n.z).toLatLon(...)
|
|
664
|
+
|
|
665
|
+
# no intersection, d < EPS_2 or fabs(j) < EPS_2 or z < EPS
|
|
666
|
+
t = _SPACE_(_no_, _intersection_, NN)
|
|
667
|
+
raise IntersectionError(point1=point1, distance1=distance1,
|
|
668
|
+
point2=point2, distance2=distance2,
|
|
669
|
+
point3=point3, distance3=distance3,
|
|
670
|
+
txt=unstr(t, z=z, useZ=useZ, wrap=wrap))
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
__all__ += _ALL_DOCS(LatLonNvectorBase, NvectorBase, sumOf) # classes
|
|
674
|
+
|
|
675
|
+
# **) MIT License
|
|
676
|
+
#
|
|
677
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
678
|
+
#
|
|
679
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
680
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
681
|
+
# to deal in the Software without restriction, including without limitation
|
|
682
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
683
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
684
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
685
|
+
#
|
|
686
|
+
# The above copyright notice and this permission notice shall be included
|
|
687
|
+
# in all copies or substantial portions of the Software.
|
|
688
|
+
#
|
|
689
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
690
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
691
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
692
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
693
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
694
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
695
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|