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.
Files changed (115) hide show
  1. PyGeodesy-24.3.24.dist-info/METADATA +272 -0
  2. PyGeodesy-24.3.24.dist-info/RECORD +115 -0
  3. PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
  4. PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
  5. pygeodesy/LICENSE +21 -0
  6. pygeodesy/__init__.py +615 -0
  7. pygeodesy/__main__.py +103 -0
  8. pygeodesy/albers.py +867 -0
  9. pygeodesy/auxilats/_CX_4.py +218 -0
  10. pygeodesy/auxilats/_CX_6.py +314 -0
  11. pygeodesy/auxilats/_CX_8.py +475 -0
  12. pygeodesy/auxilats/__init__.py +54 -0
  13. pygeodesy/auxilats/__main__.py +86 -0
  14. pygeodesy/auxilats/auxAngle.py +548 -0
  15. pygeodesy/auxilats/auxDLat.py +302 -0
  16. pygeodesy/auxilats/auxDST.py +296 -0
  17. pygeodesy/auxilats/auxLat.py +848 -0
  18. pygeodesy/auxilats/auxily.py +272 -0
  19. pygeodesy/azimuthal.py +1150 -0
  20. pygeodesy/basics.py +892 -0
  21. pygeodesy/booleans.py +2031 -0
  22. pygeodesy/cartesianBase.py +1062 -0
  23. pygeodesy/clipy.py +704 -0
  24. pygeodesy/constants.py +516 -0
  25. pygeodesy/css.py +660 -0
  26. pygeodesy/datums.py +752 -0
  27. pygeodesy/deprecated/__init__.py +61 -0
  28. pygeodesy/deprecated/bases.py +40 -0
  29. pygeodesy/deprecated/classes.py +262 -0
  30. pygeodesy/deprecated/consterns.py +54 -0
  31. pygeodesy/deprecated/datum.py +40 -0
  32. pygeodesy/deprecated/functions.py +375 -0
  33. pygeodesy/deprecated/nvector.py +48 -0
  34. pygeodesy/deprecated/rhumbBase.py +32 -0
  35. pygeodesy/deprecated/rhumbaux.py +33 -0
  36. pygeodesy/deprecated/rhumbsolve.py +33 -0
  37. pygeodesy/deprecated/rhumbx.py +33 -0
  38. pygeodesy/dms.py +986 -0
  39. pygeodesy/ecef.py +1348 -0
  40. pygeodesy/elevations.py +279 -0
  41. pygeodesy/ellipsoidalBase.py +1224 -0
  42. pygeodesy/ellipsoidalBaseDI.py +913 -0
  43. pygeodesy/ellipsoidalExact.py +343 -0
  44. pygeodesy/ellipsoidalGeodSolve.py +343 -0
  45. pygeodesy/ellipsoidalKarney.py +403 -0
  46. pygeodesy/ellipsoidalNvector.py +685 -0
  47. pygeodesy/ellipsoidalVincenty.py +590 -0
  48. pygeodesy/ellipsoids.py +2476 -0
  49. pygeodesy/elliptic.py +1198 -0
  50. pygeodesy/epsg.py +243 -0
  51. pygeodesy/errors.py +804 -0
  52. pygeodesy/etm.py +1190 -0
  53. pygeodesy/fmath.py +1013 -0
  54. pygeodesy/formy.py +1818 -0
  55. pygeodesy/frechet.py +865 -0
  56. pygeodesy/fstats.py +760 -0
  57. pygeodesy/fsums.py +1898 -0
  58. pygeodesy/gars.py +358 -0
  59. pygeodesy/geodesicw.py +581 -0
  60. pygeodesy/geodesicx/_C4_24.py +1699 -0
  61. pygeodesy/geodesicx/_C4_27.py +2395 -0
  62. pygeodesy/geodesicx/_C4_30.py +3301 -0
  63. pygeodesy/geodesicx/__init__.py +48 -0
  64. pygeodesy/geodesicx/__main__.py +91 -0
  65. pygeodesy/geodesicx/gx.py +1382 -0
  66. pygeodesy/geodesicx/gxarea.py +535 -0
  67. pygeodesy/geodesicx/gxbases.py +154 -0
  68. pygeodesy/geodesicx/gxline.py +669 -0
  69. pygeodesy/geodsolve.py +426 -0
  70. pygeodesy/geohash.py +914 -0
  71. pygeodesy/geoids.py +1884 -0
  72. pygeodesy/hausdorff.py +892 -0
  73. pygeodesy/heights.py +1155 -0
  74. pygeodesy/interns.py +687 -0
  75. pygeodesy/iters.py +545 -0
  76. pygeodesy/karney.py +919 -0
  77. pygeodesy/ktm.py +633 -0
  78. pygeodesy/latlonBase.py +1766 -0
  79. pygeodesy/lazily.py +960 -0
  80. pygeodesy/lcc.py +684 -0
  81. pygeodesy/ltp.py +1107 -0
  82. pygeodesy/ltpTuples.py +1563 -0
  83. pygeodesy/mgrs.py +721 -0
  84. pygeodesy/named.py +1324 -0
  85. pygeodesy/namedTuples.py +683 -0
  86. pygeodesy/nvectorBase.py +695 -0
  87. pygeodesy/osgr.py +781 -0
  88. pygeodesy/points.py +1686 -0
  89. pygeodesy/props.py +628 -0
  90. pygeodesy/resections.py +1048 -0
  91. pygeodesy/rhumb/__init__.py +46 -0
  92. pygeodesy/rhumb/aux_.py +397 -0
  93. pygeodesy/rhumb/bases.py +1148 -0
  94. pygeodesy/rhumb/ekx.py +563 -0
  95. pygeodesy/rhumb/solve.py +572 -0
  96. pygeodesy/simplify.py +647 -0
  97. pygeodesy/solveBase.py +472 -0
  98. pygeodesy/sphericalBase.py +724 -0
  99. pygeodesy/sphericalNvector.py +1264 -0
  100. pygeodesy/sphericalTrigonometry.py +1447 -0
  101. pygeodesy/streprs.py +627 -0
  102. pygeodesy/trf.py +2079 -0
  103. pygeodesy/triaxials.py +1484 -0
  104. pygeodesy/units.py +969 -0
  105. pygeodesy/unitsBase.py +349 -0
  106. pygeodesy/ups.py +538 -0
  107. pygeodesy/utily.py +1231 -0
  108. pygeodesy/utm.py +762 -0
  109. pygeodesy/utmups.py +318 -0
  110. pygeodesy/utmupsBase.py +517 -0
  111. pygeodesy/vector2d.py +785 -0
  112. pygeodesy/vector3d.py +968 -0
  113. pygeodesy/vector3dBase.py +1049 -0
  114. pygeodesy/webmercator.py +383 -0
  115. pygeodesy/wgrs.py +439 -0
@@ -0,0 +1,535 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ u'''Slightly enhanced versions of classes U{PolygonArea
4
+ <https://GeographicLib.SourceForge.io/1.52/python/code.html#
5
+ module-geographiclib.polygonarea>} and C{Accumulator} from
6
+ I{Karney}'s Python U{geographiclib
7
+ <https://GeographicLib.SourceForge.io/1.52/python/index.html>}.
8
+
9
+ Class L{GeodesicAreaExact} is intended to work with instances
10
+ of class L{GeodesicExact} and of I{wrapped} class C{Geodesic},
11
+ see module L{pygeodesy.karney}.
12
+
13
+ Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2023)
14
+ and licensed under the MIT/X11 License. For more information, see the
15
+ U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
16
+ '''
17
+ # make sure int/int division yields float quotient
18
+ from __future__ import division as _; del _ # PYCHOK semicolon
19
+
20
+ from pygeodesy.basics import isodd, unsigned0
21
+ from pygeodesy.constants import NAN, _0_0, _0_5, _720_0
22
+ from pygeodesy.interns import NN, _COMMASPACE_
23
+ from pygeodesy.karney import Area3Tuple, _diff182, GeodesicError, \
24
+ _norm180, _remainder, _sum2_
25
+ from pygeodesy.lazily import _ALL_DOCS, printf
26
+ from pygeodesy.named import ADict, callername, _NamedBase, pairs
27
+ from pygeodesy.props import Property, Property_RO, property_RO
28
+ # from pygeodesy.streprs import pairs # from .named
29
+
30
+ from math import fmod
31
+
32
+ __all__ = ()
33
+ __version__ = '24.03.24'
34
+
35
+
36
+ class GeodesicAreaExact(_NamedBase):
37
+ '''Area and perimeter of a geodesic polygon, an enhanced
38
+ version of I{Karney}'s Python class U{PolygonArea
39
+ <https://GeographicLib.SourceForge.io/html/python/
40
+ code.html#module-geographiclib.polygonarea>} using
41
+ the more accurate surface area.
42
+
43
+ @note: The name of this class C{*Exact} is a misnomer, see
44
+ I{Karney}'s comments at C++ attribute U{GeodesicExact._c2
45
+ <https://GeographicLib.SourceForge.io/C++/doc/
46
+ GeodesicExact_8cpp_source.html>}.
47
+ '''
48
+ _Area = None
49
+ _g_gX = None # Exact or not
50
+ _lat0 = _lon0 = \
51
+ _lat1 = _lon1 = NAN
52
+ _mask = 0
53
+ _num = 0
54
+ _Peri = None
55
+ _verbose = False
56
+ _xings = 0
57
+
58
+ def __init__(self, geodesic, polyline=False, name=NN):
59
+ '''New L{GeodesicAreaExact} instance.
60
+
61
+ @arg geodesic: A geodesic (L{GeodesicExact}, I{wrapped}
62
+ C{Geodesic} or L{GeodesicSolve}).
63
+ @kwarg polyline: If C{True}, compute the perimeter only,
64
+ otherwise area and perimeter (C{bool}).
65
+ @kwarg name: Optional name (C{str}).
66
+
67
+ @raise GeodesicError: Invalid B{C{geodesic}}.
68
+ '''
69
+ try: # results returned as L{GDict}
70
+ if not (callable(geodesic._GDictDirect) and
71
+ callable(geodesic._GDictInverse)):
72
+ raise TypeError()
73
+ except (AttributeError, TypeError):
74
+ raise GeodesicError(geodesic=geodesic)
75
+
76
+ self._g_gX = g = geodesic
77
+ # use the class-level Caps since the values
78
+ # differ between GeodesicExact and Geodesic
79
+ self._mask = g.DISTANCE | g.LATITUDE | g.LONGITUDE
80
+ self._Peri = _Accumulator(name='_Peri')
81
+ if not polyline: # perimeter and area
82
+ self._mask |= g.AREA | g.LONG_UNROLL
83
+ self._Area = _Accumulator(name='_Area')
84
+ if g.debug: # PYCHOK no cover
85
+ self.verbose = True # debug as verbosity
86
+ if name:
87
+ self.name = name
88
+
89
+ def AddEdge(self, azi, s):
90
+ '''Add another polygon edge.
91
+
92
+ @arg azi: Azimuth at the current point (compass
93
+ C{degrees360}).
94
+ @arg s: Length of the edge (C{meter}).
95
+ '''
96
+ if self.num < 1:
97
+ raise GeodesicError(num=self.num)
98
+ r = self._Direct(azi, s)
99
+ p = self._Peri.Add(s)
100
+ if self._Area:
101
+ a = self._Area.Add(r.S12)
102
+ self._xings += r.xing
103
+ else:
104
+ a = NAN
105
+ self._lat1 = r.lat2
106
+ self._lon1 = r.lon2
107
+ self._num += 1
108
+ if self.verbose: # PYCHOK no cover
109
+ self._print(self.num, p, a, r, lat1=r.lat2, lon1=r.lon2,
110
+ azi=azi, s=s)
111
+ return self.num
112
+
113
+ def AddPoint(self, lat, lon):
114
+ '''Add another polygon point.
115
+
116
+ @arg lat: Latitude of the point (C{degrees}).
117
+ @arg lon: Longitude of the point (C{degrees}).
118
+ '''
119
+ if self.num > 0:
120
+ r = self._Inverse(self.lat1, self.lon1, lat, lon)
121
+ s = r.s12
122
+ p = self._Peri.Add(s)
123
+ if self._Area:
124
+ a = self._Area.Add(r.S12)
125
+ self._xings += r.xing
126
+ else:
127
+ a = NAN
128
+ else:
129
+ self._lat0 = lat
130
+ self._lon0 = lon
131
+ a = p = s = _0_0
132
+ r = None
133
+ self._lat1 = lat
134
+ self._lon1 = lon
135
+ self._num += 1
136
+ if self.verbose: # PYCHOK no cover
137
+ self._print(self.num, p, a, r, lat1=lat, lon1=lon, s=s)
138
+ return self.num
139
+
140
+ @Property_RO
141
+ def area0x(self):
142
+ '''Get the ellipsoid's surface area (C{meter} I{squared}),
143
+ more accurate for very I{oblate} ellipsoids.
144
+ '''
145
+ return self.ellipsoid.areax # not .area!
146
+
147
+ area0 = area0x # for C{geographiclib} compatibility
148
+
149
+ def Compute(self, reverse=False, sign=True):
150
+ '''Compute the accumulated perimeter and area.
151
+
152
+ @kwarg reverse: If C{True}, clockwise traversal counts as a
153
+ positive area instead of counter-clockwise
154
+ (C{bool}).
155
+ @kwarg sign: If C{True}, return a signed result for the area if
156
+ the polygon is traversed in the "wrong" direction
157
+ instead of returning the area for the rest of the
158
+ earth.
159
+
160
+ @return: L{Area3Tuple}C{(number, perimeter, area)} with the
161
+ number of points, the perimeter in C{meter} and the
162
+ area in C{meter**2}. The perimeter includes the
163
+ length of a final edge, connecting the current to
164
+ the initial point, if this polygon was initialized
165
+ with C{polyline=False}. For perimeter only, i.e.
166
+ C{polyline=True}, area is C{NAN}.
167
+
168
+ @note: Arbitrarily complex polygons are allowed. In the case
169
+ of self-intersecting polygons, the area is accumulated
170
+ "algebraically". E.g., the areas of the 2 loops in a
171
+ I{figure-8} polygon will partially cancel.
172
+
173
+ @note: More points and edges can be added after this call.
174
+ '''
175
+ r, n = None, self.num
176
+ if n < 2:
177
+ p = _0_0
178
+ a = NAN if self.polyline else p
179
+ elif self._Area:
180
+ r = self._Inverse(self.lat1, self.lon1, self.lat0, self.lon0)
181
+ a = self._reduced(r.S12, reverse, sign, r.xing)
182
+ p = self._Peri.Sum(r.s12)
183
+ else:
184
+ p = self._Peri.Sum()
185
+ a = NAN
186
+ if self.verbose: # PYCHOK no cover
187
+ self._print(n, p, a, r, lat0=self.lat0, lon0=self.lon0)
188
+ return Area3Tuple(n, p, a)
189
+
190
+ def _Direct(self, azi, s):
191
+ '''(INTERNAL) Edge helper.
192
+ '''
193
+ lon1 = self.lon1
194
+ r = self._g_gX._GDictDirect(self.lat1, lon1, azi, False, s, self._mask)
195
+ if self._Area: # aka transitDirect
196
+ # Count crossings of prime meridian exactly as
197
+ # int(ceil(lon2 / 360)) - int(ceil(lon1 / 360))
198
+ # Since we only need the parity of the result we
199
+ # can use std::remquo but this is buggy with g++
200
+ # 4.8.3 and requires C++11. So instead we do:
201
+ lon1 = fmod( lon1, _720_0) # r.lon1
202
+ lon2 = fmod(r.lon2, _720_0)
203
+ # int(True) == 1, int(False) == 0
204
+ r.set_(xing=int(lon2 > 360 or -360 < lon2 <= 0) -
205
+ int(lon1 > 360 or -360 < lon1 <= 0))
206
+ return r
207
+
208
+ @Property_RO
209
+ def ellipsoid(self):
210
+ '''Get this area's ellipsoid (C{Ellipsoid[2]}).
211
+ '''
212
+ return self._g_gX.ellipsoid
213
+
214
+ @Property_RO
215
+ def geodesic(self):
216
+ '''Get this area's geodesic object (C{Geodesic[Exact]}).
217
+ '''
218
+ return self._g_gX
219
+
220
+ earth = geodesic # for C{geographiclib} compatibility
221
+
222
+ def _Inverse(self, lat1, lon1, lat2, lon2):
223
+ '''(INTERNAL) Point helper.
224
+ '''
225
+ r = self._g_gX._GDictInverse(lat1, lon1, lat2, lon2, self._mask)
226
+ if self._Area: # aka transit
227
+ # count crossings of prime meridian as +1 or -1
228
+ # if in east or west direction, otherwise 0
229
+ lon1 = _norm180(lon1)
230
+ lon2 = _norm180(lon2)
231
+ lon12, _ = _diff182(lon1, lon2)
232
+ r.set_(xing=int(lon12 > 0 and lon1 <= 0 and lon2 > 0) or
233
+ -int(lon12 < 0 and lon2 <= 0 and lon1 > 0))
234
+ return r
235
+
236
+ @property_RO
237
+ def lat0(self):
238
+ '''Get the first point's latitude (C{degrees}).
239
+ '''
240
+ return self._lat0
241
+
242
+ @property_RO
243
+ def lat1(self):
244
+ '''Get the most recent point's latitude (C{degrees}).
245
+ '''
246
+ return self._lat1
247
+
248
+ @property_RO
249
+ def lon0(self):
250
+ '''Get the first point's longitude (C{degrees}).
251
+ '''
252
+ return self._lon0
253
+
254
+ @property_RO
255
+ def lon1(self):
256
+ '''Get the most recent point's longitude (C{degrees}).
257
+ '''
258
+ return self._lon1
259
+
260
+ @property_RO
261
+ def num(self):
262
+ '''Get the current number of points (C{int}).
263
+ '''
264
+ return self._num
265
+
266
+ @Property_RO
267
+ def polyline(self):
268
+ '''Is this perimeter only (C{bool}), area NAN?
269
+ '''
270
+ return self._Area is None
271
+
272
+ def _print(self, n, p, a, r, **kwds): # PYCHOK no cover
273
+ '''(INTERNAL) Print a verbose line.
274
+ '''
275
+ d = ADict(p=p, s12=r.s12 if r else NAN, **kwds)
276
+ if self._Area:
277
+ d.set_(a=a, S12=r.S12 if r else NAN)
278
+ t = _COMMASPACE_.join(pairs(d, prec=10))
279
+ printf('%s %s: %s (%s)', self.named2, n, t, callername(up=2))
280
+
281
+ def _reduced(self, S12, reverse, sign, xing):
282
+ '''(INTERNAL) Accumulate and reduce area to allowed range.
283
+ '''
284
+ a0 = self.area0x
285
+ A = _Accumulator(self._Area)
286
+ _ = A.Add(S12)
287
+ a = A.Remainder(a0) # clockwise
288
+ if isodd(self._xings + xing):
289
+ a = A.Add((a0 if a < 0 else -a0) * _0_5)
290
+ if not reverse:
291
+ a = A.Negate() # counter-clockwise
292
+ # (-area0x/2, area0x/2] if sign else [0, area0x)
293
+ a0_ = a0 if sign else (a0 * _0_5)
294
+ if a > a0_:
295
+ a = A.Add(-a0)
296
+ elif a <= -a0_:
297
+ a = A.Add( a0)
298
+ return unsigned0(a)
299
+
300
+ def Reset(self):
301
+ '''Reset this polygon to empty.
302
+ '''
303
+ if self._Area:
304
+ self._Area.Reset()
305
+ self._Peri.Reset()
306
+ self._lat0 = self._lon0 = \
307
+ self._lat1 = self._lon1 = NAN
308
+ self._num = self._xings = n = 0
309
+ if self.verbose: # PYCHOK no cover
310
+ printf('%s %s: (%s)', self.named2, n, self.Reset.__name__)
311
+ return n
312
+
313
+ Clear = Reset
314
+
315
+ def TestEdge(self, azi, s, reverse=False, sign=True):
316
+ '''Compute the properties for a tentative, additional edge
317
+
318
+ @arg azi: Azimuth at the current the point (compass C{degrees}).
319
+ @arg s: Length of the edge (C{meter}).
320
+ @kwarg reverse: If C{True}, clockwise traversal counts as a
321
+ positive area instead of counter-clockwise
322
+ (C{bool}).
323
+ @kwarg sign: If C{True}, return a signed result for the area if
324
+ the polygon is traversed in the "wrong" direction
325
+ instead of returning the area for the rest of the
326
+ earth.
327
+
328
+ @return: L{Area3Tuple}C{(number, perimeter, area)}.
329
+
330
+ @raise GeodesicError: No points.
331
+ '''
332
+ n = self.num + 1
333
+ p = self._Peri.Sum(s)
334
+ if self.polyline:
335
+ a, r = NAN, None
336
+ elif n < 2:
337
+ raise GeodesicError(num=self.num)
338
+ else:
339
+ d = self._Direct(azi, s)
340
+ r = self._Inverse(d.lat2, d.lon2, self.lat0, self.lon0)
341
+ a = self._reduced(d.S12 + r.S12, reverse, sign, d.xing + r.xing)
342
+ p += r.s12
343
+ if self.verbose: # PYCHOK no cover
344
+ self._print(n, p, a, r, azi=azi, s=s)
345
+ return Area3Tuple(n, p, a)
346
+
347
+ def TestPoint(self, lat, lon, reverse=False, sign=True):
348
+ '''Compute the properties for a tentative, additional vertex
349
+
350
+ @arg lat: Latitude of the point (C{degrees}).
351
+ @arg lon: Longitude of the point (C{degrees}).
352
+ @kwarg reverse: If C{True}, clockwise traversal counts as a
353
+ positive area instead of counter-clockwise
354
+ (C{bool}).
355
+ @kwarg sign: If C{True}, return a signed result for the area if
356
+ the polygon is traversed in the "wrong" direction
357
+ instead of returning the area for the rest of the
358
+ earth.
359
+
360
+ @return: L{Area3Tuple}C{(number, perimeter, area)}.
361
+ '''
362
+ r, n = None, self.num + 1
363
+ if n < 2:
364
+ p = _0_0
365
+ a = NAN if self.polyline else p
366
+ else:
367
+ i = self._Inverse(self.lat1, self.lon1, lat, lon)
368
+ p = self._Peri.Sum(i.s12)
369
+ if self._Area:
370
+ r = self._Inverse(lat, lon, self.lat0, self.lon0)
371
+ a = self._reduced(i.S12 + r.S12, reverse, sign, i.xing + r.xing)
372
+ p += r.s12
373
+ else:
374
+ a = NAN
375
+ if self.verbose: # PYCHOK no cover
376
+ self._print(n, p, a, r, lat=lat, lon=lon)
377
+ return Area3Tuple(n, p, a)
378
+
379
+ def toStr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature
380
+ '''Return this C{GeodesicExactArea} as string.
381
+
382
+ @kwarg prec: The C{float} precision, number of decimal digits (0..9).
383
+ Trailing zero decimals are stripped for B{C{prec}} values
384
+ of 1 and above, but kept for negative B{C{prec}} values.
385
+ @kwarg sep: Separator to join (C{str}).
386
+
387
+ @return: Area items (C{str}).
388
+ '''
389
+ n, p, a = self.Compute()
390
+ d = dict(geodesic=self.geodesic, num=n, area=a,
391
+ perimeter=p, polyline=self.polyline)
392
+ return sep.join(pairs(d, prec=prec))
393
+
394
+ @Property
395
+ def verbose(self):
396
+ '''Get the C{verbose} option (C{bool}).
397
+ '''
398
+ return self._verbose
399
+
400
+ @verbose.setter # PYCHOK setter!
401
+ def verbose(self, verbose): # PYCHOK no cover
402
+ '''Set the C{verbose} option (C{bool}) to print
403
+ a message after each method invokation.
404
+ '''
405
+ self._verbose = bool(verbose)
406
+
407
+
408
+ class PolygonArea(GeodesicAreaExact):
409
+ '''For C{geographiclib} compatibility, sub-class of L{GeodesicAreaExact}.
410
+ '''
411
+ def __init__(self, earth, polyline=False, name=NN):
412
+ '''New L{PolygonArea} instance.
413
+
414
+ @arg earth: A geodesic (L{GeodesicExact}, I{wrapped}
415
+ C{Geodesic} or L{GeodesicSolve}).
416
+ @kwarg polyline: If C{True} perimeter only, otherwise
417
+ area and perimeter (C{bool}).
418
+ @kwarg name: Optional name (C{str}).
419
+
420
+ @raise GeodesicError: Invalid B{C{earth}}.
421
+ '''
422
+ GeodesicAreaExact.__init__(self, earth, polyline=polyline, name=name)
423
+
424
+
425
+ class _Accumulator(_NamedBase):
426
+ '''Like C{math.fsum}, but allowing a running sum.
427
+
428
+ Original from I{Karney}'s U{geographiclib
429
+ <https://PyPI.org/project/geographiclib>}C{.accumulator},
430
+ enhanced to return the current sum by most methods.
431
+ '''
432
+ _n = 0 # len()
433
+ _s = _t = _0_0
434
+
435
+ def __init__(self, y=0, name=NN):
436
+ '''New L{_Accumulator}.
437
+ '''
438
+ if isinstance(y, _Accumulator):
439
+ self._s, self._t, self._n = y._s, y._t, 1
440
+ elif y:
441
+ self._s, self._n = float(y), 1
442
+ if name:
443
+ self.name = name
444
+
445
+ def Add(self, y):
446
+ '''Add a value.
447
+
448
+ @return: Current C{sum}.
449
+ '''
450
+ self._n += 1
451
+ self._s, self._t = _sum2_(self._s, self._t, y)
452
+ return self._s # current .Sum()
453
+
454
+ def Negate(self):
455
+ '''Negate sum.
456
+
457
+ @return: Current C{sum}.
458
+ '''
459
+ self._s = s = -self._s
460
+ self._t = -self._t
461
+ return s # current .Sum()
462
+
463
+ @property_RO
464
+ def num(self):
465
+ '''Get the current number of C{Add}itions (C{int}).
466
+ '''
467
+ return self._n
468
+
469
+ def Remainder(self, y):
470
+ '''Remainder on division by B{C{y}}.
471
+
472
+ @return: Remainder of C{sum} / B{C{y}}.
473
+ '''
474
+ self._s = _remainder(self._s, y)
475
+ # self._t = _remainder(self._t, y)
476
+ self._n = -1
477
+ return self.Add(_0_0)
478
+
479
+ def Reset(self, y=0):
480
+ '''Set value from argument.
481
+ '''
482
+ self._n, self._s, self._t = 0, float(y), _0_0
483
+
484
+ Set = Reset
485
+
486
+ def Sum(self, y=0):
487
+ '''Return C{sum + B{y}}.
488
+
489
+ @note: B{C{y}} is included in the returned
490
+ result, but I{not} accumulated.
491
+ '''
492
+ if y:
493
+ s = _Accumulator(self, name='_Sum')
494
+ s.Add(y)
495
+ else:
496
+ s = self
497
+ return s._s
498
+
499
+ def toStr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature
500
+ '''Return this C{_Accumulator} as string.
501
+
502
+ @kwarg prec: The C{float} precision, number of decimal digits (0..9).
503
+ Trailing zero decimals are stripped for B{C{prec}} values
504
+ of 1 and above, but kept for negative B{C{prec}} values.
505
+ @kwarg sep: Separator to join (C{str}).
506
+
507
+ @return: Accumulator (C{str}).
508
+ '''
509
+ d = dict(n=self.num, s=self._s, t=self._t)
510
+ return sep.join(pairs(d, prec=prec))
511
+
512
+
513
+ __all__ += _ALL_DOCS(GeodesicAreaExact, PolygonArea)
514
+
515
+ # **) MIT License
516
+ #
517
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
518
+ #
519
+ # Permission is hereby granted, free of charge, to any person obtaining a
520
+ # copy of this software and associated documentation files (the "Software"),
521
+ # to deal in the Software without restriction, including without limitation
522
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
523
+ # and/or sell copies of the Software, and to permit persons to whom the
524
+ # Software is furnished to do so, subject to the following conditions:
525
+ #
526
+ # The above copyright notice and this permission notice shall be included
527
+ # in all copies or substantial portions of the Software.
528
+ #
529
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
530
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
531
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
532
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
533
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
534
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
535
+ # OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,154 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''(INTERNAL) Private L{geodesicx} base class, functions and constants.
5
+
6
+ Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2023)
7
+ and licensed under the MIT/X11 License. For more information, see the
8
+ U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
9
+ '''
10
+
11
+ from pygeodesy.basics import isodd, _MODS
12
+ from pygeodesy.constants import _0_0, _100_0
13
+ from pygeodesy.errors import _or, _xkwds_item2, _not_
14
+ from pygeodesy.fmath import hypot as _hypot
15
+ # from pygeodesy.interns import _not_ # from .errors
16
+ from pygeodesy.karney import _CapsBase, GeodesicError, _2cos2x, _sum2_
17
+ # from pygeodesy.lazily import _MODS, printf # .basics, _MODS
18
+
19
+ from math import ldexp as _ldexp
20
+
21
+ __all__ = ()
22
+ __version__ = '24.03.15'
23
+
24
+ # valid C{nC4}s and C{C4order}s, see _xnC4 below
25
+ _nC4s = {24: 2900, 27: 4032, 30: 5425}
26
+
27
+
28
+ class _GeodesicBase(_CapsBase): # in .geodsolve
29
+ '''(INTERNAL) Base class for C{[_]Geodesic*Exact}.
30
+ '''
31
+ # def toRepr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature
32
+ # '''Return this C{GeodesicExact*} items string.
33
+ #
34
+ # @kwarg prec: The C{float} precision, number of decimal digits (0..9).
35
+ # Trailing zero decimals are stripped for B{C{prec}} values
36
+ # of 1 and above, but kept for negative B{C{prec}} values.
37
+ # @kwarg sep: Separator to join (C{str}).
38
+ #
39
+ # @return: C{GeodesicExact*} (C{str}).
40
+ # '''
41
+ # return Fmt.PAREN(self.named, self.toStr(prec=prec, sep=sep))
42
+ pass
43
+
44
+
45
+ class _Gfloats(dict):
46
+ '''(INTERNAL) Uniquify floats.
47
+ '''
48
+ n = 0 # total number of floats
49
+ nC4 = 0
50
+
51
+ def __init__(self, nC4): # PYCHOK signature
52
+ self.nC4 = nC4
53
+
54
+ def __call__(self, fs):
55
+ '''Return a tuple of "uniquified" floats.
56
+ '''
57
+ self.n += len(fs)
58
+ _f = self.setdefault
59
+ return tuple(_f(f, f) for f in map(float, fs)) # PYCHOK as attr
60
+
61
+ def prints(self):
62
+ n, u = self.n, len(self.keys())
63
+ d = (n - u) * _100_0 / n
64
+ _MODS.lazily.printf('_CX_%d: n=%d, u=%d, d=%.1f%%', self.nC4, n, u, d) # XXX
65
+
66
+
67
+ def _cosSeries(c4s, sx, cx): # PYCHOK shared .geodesicx.gx and -.gxline
68
+ '''(INTERNAL) I{Karney}'s cosine series expansion using U{Clenshaw
69
+ summation<https://WikiPedia.org/wiki/Clenshaw_algorithm>}.
70
+ '''
71
+ ar = _2cos2x(cx, sx)
72
+ y0 = t0 = y1 = t1 = _0_0
73
+ c4 = list(c4s)
74
+ _c4 = c4.pop
75
+ _s2 = _sum2_
76
+ if isodd(len(c4)):
77
+ y0 = _c4()
78
+ while c4:
79
+ # y1 = ar * y0 - y1 + c4.pop()
80
+ # y0 = ar * y1 - y0 + c4.pop()
81
+ y1, t1 = _s2(ar * y0, ar * t0, -y1, -t1, _c4())
82
+ y0, t0 = _s2(ar * y1, ar * t1, -y0, -t0, _c4())
83
+ # s = cx * (y0 - y1)
84
+ s, _ = _s2(cx * y0, _0_0, cx * t0, -cx * y1, -cx * t1)
85
+ return s
86
+
87
+
88
+ _f = float # in _f2 and .geodesicx._C4_24, _27 and _30
89
+
90
+
91
+ def _f2(hi, lo): # in .geodesicx._C4_24, _27 and _30
92
+ '''(INTERNAL) For C{_coeffs}.
93
+ '''
94
+ return _ldexp(_f(hi), 52) + _f(lo)
95
+
96
+
97
+ def _sincos12(sin1, cos1, sin2, cos2, sineg0=False): # PYCHOK shared
98
+ '''(INTERNAL) Compute the sine and cosine of angle
99
+ M{ang12 = atan2(sin2, cos2) - atan2(sin1, cos1)}.
100
+
101
+ Negate C{sin1} to get C{sin12} and C{cos12} of the sum
102
+ M{ang12 = atan2(sin2, cos2) + atan2(sin1, cos1)}.
103
+
104
+ @kwarg sineg0: If C{True}, make negative C{sin12} zero (C{bool}).
105
+
106
+ @return: 2-Tuple C{(sin12, cos12)}.
107
+ '''
108
+ s = sin2 * cos1 - sin1 * cos2
109
+ c = cos2 * cos1 + sin1 * sin2
110
+ if sineg0 and s < 0:
111
+ s = _0_0 # max(s, _0_0) or NEG0?
112
+ return s, c
113
+
114
+
115
+ def _sin1cos2(sin1, cos1, sin2, cos2): # PYCHOK shared
116
+ '''(INTERNAL) Compute the C{sin1 * cos2} sine and its cosine.
117
+
118
+ @return: 2-Tuple C{(sin1 * cos2, hypot(sin1 * sin2, cos1)}.
119
+ '''
120
+ s = sin1 * cos2
121
+ c = _hypot(sin1 * sin2, cos1)
122
+ return s, c # _norm2(s, c)
123
+
124
+
125
+ def _xnC4(**name_nC4):
126
+ '''(INTERNAL) Validate C{C4order}.
127
+ '''
128
+ n, nC4 = _xkwds_item2(name_nC4)
129
+ if nC4 not in _nC4s or not isinstance(nC4, int):
130
+ raise GeodesicError(n, nC4, txt=_not_(_or(*map(str, _nC4s))))
131
+ return _nC4s[nC4]
132
+
133
+
134
+ # **) MIT License
135
+ #
136
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
137
+ #
138
+ # Permission is hereby granted, free of charge, to any person obtaining a
139
+ # copy of this software and associated documentation files (the "Software"),
140
+ # to deal in the Software without restriction, including without limitation
141
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
142
+ # and/or sell copies of the Software, and to permit persons to whom the
143
+ # Software is furnished to do so, subject to the following conditions:
144
+ #
145
+ # The above copyright notice and this permission notice shall be included
146
+ # in all copies or substantial portions of the Software.
147
+ #
148
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
149
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
150
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
151
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
152
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
153
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
154
+ # OTHER DEALINGS IN THE SOFTWARE.