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,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.
|