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,1447 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Spherical, C{trigonometry}-based geodesy.
|
|
5
|
+
|
|
6
|
+
Trigonometric classes geodetic (lat-/longitude) L{LatLon} and
|
|
7
|
+
geocentric (ECEF) L{Cartesian} and functions L{areaOf}, L{intersection},
|
|
8
|
+
L{intersections2}, L{isPoleEnclosedBy}, L{meanOf}, L{nearestOn3} and
|
|
9
|
+
L{perimeterOf}, I{all spherical}.
|
|
10
|
+
|
|
11
|
+
Pure Python implementation of geodetic (lat-/longitude) methods using
|
|
12
|
+
spherical trigonometry, transcoded from JavaScript originals by
|
|
13
|
+
I{(C) Chris Veness 2011-2016} published under the same MIT Licence**, see
|
|
14
|
+
U{Latitude/Longitude<https://www.Movable-Type.co.UK/scripts/latlong.html>}.
|
|
15
|
+
'''
|
|
16
|
+
# make sure int/int division yields float quotient, see .basics
|
|
17
|
+
from __future__ import division as _; del _ # PYCHOK semicolon
|
|
18
|
+
|
|
19
|
+
from pygeodesy.basics import copysign0, map1, signOf
|
|
20
|
+
from pygeodesy.constants import EPS, EPS1, EPS4, PI, PI2, PI_2, PI_4, R_M, \
|
|
21
|
+
isnear0, isnear1, isnon0, _0_0, _0_5, \
|
|
22
|
+
_1_0, _2_0, _90_0
|
|
23
|
+
from pygeodesy.datums import _ellipsoidal_datum, _mean_radius
|
|
24
|
+
from pygeodesy.errors import _AssertionError, CrossError, crosserrors, \
|
|
25
|
+
_TypeError, _ValueError, IntersectionError, \
|
|
26
|
+
_xError, _xkwds, _xkwds_get, _xkwds_pop2
|
|
27
|
+
from pygeodesy.fmath import favg, fdot, fmean, hypot
|
|
28
|
+
from pygeodesy.fsums import Fsum, fsum, fsumf_
|
|
29
|
+
from pygeodesy.formy import antipode_, bearing_, _bearingTo2, excessAbc_, \
|
|
30
|
+
excessGirard_, excessLHuilier_, opposing_, _radical2, \
|
|
31
|
+
vincentys_
|
|
32
|
+
from pygeodesy.interns import _1_, _2_, _coincident_, _composite_, _colinear_, \
|
|
33
|
+
_concentric_, _convex_, _end_, _infinite_, _invalid_,\
|
|
34
|
+
_line_, _near_, _not_, _null_, _parallel_, _point_, \
|
|
35
|
+
_SPACE_, _too_
|
|
36
|
+
from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _ALL_OTHER
|
|
37
|
+
# from pygeodesy.named import notImplemented # from .points
|
|
38
|
+
# from pygeodesy.nvectorBase import NvectorBase, sumOf # _MODE
|
|
39
|
+
from pygeodesy.namedTuples import LatLon2Tuple, LatLon3Tuple, NearestOn3Tuple, \
|
|
40
|
+
Triangle7Tuple, Triangle8Tuple
|
|
41
|
+
from pygeodesy.points import ispolar, nearestOn5 as _nearestOn5, \
|
|
42
|
+
Fmt as _Fmt, notImplemented # XXX shadowed
|
|
43
|
+
from pygeodesy.props import deprecated_function, deprecated_method
|
|
44
|
+
from pygeodesy.sphericalBase import _m2radians, CartesianSphericalBase, \
|
|
45
|
+
_intersecant2, LatLonSphericalBase, \
|
|
46
|
+
_rads3, _radians2m, _trilaterate5
|
|
47
|
+
# from pygeodesy.streprs import Fmt as _Fmt # from .points XXX shadowed
|
|
48
|
+
from pygeodesy.units import Bearing_, Height, _isDegrees, _isRadius, Lam_, \
|
|
49
|
+
Phi_, Radius_, Scalar
|
|
50
|
+
from pygeodesy.utily import acos1, asin1, atan1d, atan2d, degrees90, degrees180, \
|
|
51
|
+
degrees2m, m2radians, radiansPI2, sincos2_, tan_2, \
|
|
52
|
+
unrollPI, _unrollon, _unrollon3, _Wrap, wrap180, wrapPI
|
|
53
|
+
from pygeodesy.vector3d import sumOf, Vector3d
|
|
54
|
+
|
|
55
|
+
from math import asin, atan2, cos, degrees, fabs, radians, sin
|
|
56
|
+
|
|
57
|
+
__all__ = _ALL_LAZY.sphericalTrigonometry
|
|
58
|
+
__version__ = '24.02.18'
|
|
59
|
+
|
|
60
|
+
_PI_EPS4 = PI - EPS4
|
|
61
|
+
if _PI_EPS4 >= PI:
|
|
62
|
+
raise _AssertionError(EPS4=EPS4, PI=PI, PI_EPS4=_PI_EPS4)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Cartesian(CartesianSphericalBase):
|
|
66
|
+
'''Extended to convert geocentric, L{Cartesian} points to
|
|
67
|
+
spherical, geodetic L{LatLon}.
|
|
68
|
+
'''
|
|
69
|
+
|
|
70
|
+
def toLatLon(self, **LatLon_and_kwds): # PYCHOK LatLon=LatLon
|
|
71
|
+
'''Convert this cartesian point to a C{spherical} geodetic point.
|
|
72
|
+
|
|
73
|
+
@kwarg LatLon_and_kwds: Optional L{LatLon} and L{LatLon} keyword
|
|
74
|
+
arguments. Use C{B{LatLon}=...} to override
|
|
75
|
+
this L{LatLon} class or specify C{B{LatLon}=None}.
|
|
76
|
+
|
|
77
|
+
@return: The geodetic point (L{LatLon}) or if B{C{LatLon}} is C{None},
|
|
78
|
+
an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
|
|
79
|
+
with C{C} and C{M} if available.
|
|
80
|
+
|
|
81
|
+
@raise TypeError: Invalid B{C{LatLon_and_kwds}} argument.
|
|
82
|
+
'''
|
|
83
|
+
kwds = _xkwds(LatLon_and_kwds, LatLon=LatLon, datum=self.datum)
|
|
84
|
+
return CartesianSphericalBase.toLatLon(self, **kwds)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class LatLon(LatLonSphericalBase):
|
|
88
|
+
'''New point on a spherical earth model, based on trigonometry formulae.
|
|
89
|
+
'''
|
|
90
|
+
|
|
91
|
+
def _ab1_ab2_db5(self, other, wrap):
|
|
92
|
+
'''(INTERNAL) Helper for several methods.
|
|
93
|
+
'''
|
|
94
|
+
a1, b1 = self.philam
|
|
95
|
+
a2, b2 = self.others(other, up=2).philam
|
|
96
|
+
if wrap:
|
|
97
|
+
a2, b2 = _Wrap.philam(a2, b2)
|
|
98
|
+
db, b2 = unrollPI(b1, b2, wrap=wrap)
|
|
99
|
+
else: # unrollPI shortcut
|
|
100
|
+
db = b2 - b1
|
|
101
|
+
return a1, b1, a2, b2, db
|
|
102
|
+
|
|
103
|
+
def alongTrackDistanceTo(self, start, end, radius=R_M, wrap=False):
|
|
104
|
+
'''Compute the (signed) distance from the start to the closest
|
|
105
|
+
point on the great circle line defined by a start and an
|
|
106
|
+
end point.
|
|
107
|
+
|
|
108
|
+
That is, if a perpendicular is drawn from this point to the
|
|
109
|
+
great circle line, the along-track distance is the distance
|
|
110
|
+
from the start point to the point where the perpendicular
|
|
111
|
+
crosses the line.
|
|
112
|
+
|
|
113
|
+
@arg start: Start point of the great circle line (L{LatLon}).
|
|
114
|
+
@arg end: End point of the great circle line (L{LatLon}).
|
|
115
|
+
@kwarg radius: Mean earth radius (C{meter}) or C{None}.
|
|
116
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
117
|
+
the B{C{start}} and B{C{end}} point (C{bool}).
|
|
118
|
+
|
|
119
|
+
@return: Distance along the great circle line (C{radians}
|
|
120
|
+
if C{B{radius} is None} or C{meter}, same units
|
|
121
|
+
as B{C{radius}}), positive if I{after} the
|
|
122
|
+
B{C{start}} toward the B{C{end}} point of the
|
|
123
|
+
line, I{negative} if before or C{0} if at the
|
|
124
|
+
B{C{start}} point.
|
|
125
|
+
|
|
126
|
+
@raise TypeError: Invalid B{C{start}} or B{C{end}} point.
|
|
127
|
+
|
|
128
|
+
@raise ValueError: Invalid B{C{radius}}.
|
|
129
|
+
'''
|
|
130
|
+
r, x, b = self._a_x_b3(start, end, radius, wrap)
|
|
131
|
+
cx = cos(x)
|
|
132
|
+
return _0_0 if isnear0(cx) else \
|
|
133
|
+
_radians2m(copysign0(acos1(cos(r) / cx), cos(b)), radius)
|
|
134
|
+
|
|
135
|
+
def _a_x_b3(self, start, end, radius, wrap):
|
|
136
|
+
'''(INTERNAL) Helper for .along-/crossTrackDistanceTo.
|
|
137
|
+
'''
|
|
138
|
+
s = self.others(start=start)
|
|
139
|
+
e = self.others(end=end)
|
|
140
|
+
s, e, w = _unrollon3(self, s, e, wrap)
|
|
141
|
+
|
|
142
|
+
r = Radius_(radius)
|
|
143
|
+
r = s.distanceTo(self, r, wrap=w) / r
|
|
144
|
+
|
|
145
|
+
b = radians(s.initialBearingTo(self, wrap=w)
|
|
146
|
+
- s.initialBearingTo(e, wrap=w))
|
|
147
|
+
x = asin(sin(r) * sin(b))
|
|
148
|
+
return r, x, -b
|
|
149
|
+
|
|
150
|
+
@deprecated_method
|
|
151
|
+
def bearingTo(self, other, wrap=False, raiser=False): # PYCHOK no cover
|
|
152
|
+
'''DEPRECATED, use method L{initialBearingTo}.
|
|
153
|
+
'''
|
|
154
|
+
return self.initialBearingTo(other, wrap=wrap, raiser=raiser)
|
|
155
|
+
|
|
156
|
+
def crossingParallels(self, other, lat, wrap=False):
|
|
157
|
+
'''Return the pair of meridians at which a great circle defined
|
|
158
|
+
by this and an other point crosses the given latitude.
|
|
159
|
+
|
|
160
|
+
@arg other: The other point defining great circle (L{LatLon}).
|
|
161
|
+
@arg lat: Latitude at the crossing (C{degrees}).
|
|
162
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
|
|
163
|
+
B{C{other}} point (C{bool}).
|
|
164
|
+
|
|
165
|
+
@return: 2-Tuple C{(lon1, lon2)}, both in C{degrees180} or
|
|
166
|
+
C{None} if the great circle doesn't reach B{C{lat}}.
|
|
167
|
+
'''
|
|
168
|
+
a1, b1, a2, b2, db = self._ab1_ab2_db5(other, wrap)
|
|
169
|
+
sa, ca, sa1, ca1, \
|
|
170
|
+
sa2, ca2, sdb, cdb = sincos2_(radians(lat), a1, a2, db)
|
|
171
|
+
sa1 *= ca2 * ca
|
|
172
|
+
|
|
173
|
+
x = sa1 * sdb
|
|
174
|
+
y = sa1 * cdb - ca1 * sa2 * ca
|
|
175
|
+
z = ca1 * sdb * ca2 * sa
|
|
176
|
+
|
|
177
|
+
h = hypot(x, y)
|
|
178
|
+
if h < EPS or fabs(z) > h: # PYCHOK no cover
|
|
179
|
+
return None # great circle doesn't reach latitude
|
|
180
|
+
|
|
181
|
+
m = atan2(-y, x) + b1 # longitude at max latitude
|
|
182
|
+
d = acos1(z / h) # delta longitude to intersections
|
|
183
|
+
return degrees180(m - d), degrees180(m + d)
|
|
184
|
+
|
|
185
|
+
def crossTrackDistanceTo(self, start, end, radius=R_M, wrap=False):
|
|
186
|
+
'''Compute the (signed) distance from this point to
|
|
187
|
+
the great circle defined by a start and an end point.
|
|
188
|
+
|
|
189
|
+
@arg start: Start point of the great circle line (L{LatLon}).
|
|
190
|
+
@arg end: End point of the great circle line (L{LatLon}).
|
|
191
|
+
@kwarg radius: Mean earth radius (C{meter}) or C{None}.
|
|
192
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
193
|
+
the B{C{start}} and B{C{end}} point (C{bool}).
|
|
194
|
+
|
|
195
|
+
@return: Distance to the great circle (C{radians} if
|
|
196
|
+
B{C{radius}} or C{meter}, same units as
|
|
197
|
+
B{C{radius}}), I{negative} if to the left or
|
|
198
|
+
I{positive} if to the right of the line.
|
|
199
|
+
|
|
200
|
+
@raise TypeError: If B{C{start}} or B{C{end}} is not L{LatLon}.
|
|
201
|
+
|
|
202
|
+
@raise ValueError: Invalid B{C{radius}}.
|
|
203
|
+
'''
|
|
204
|
+
_, x, _ = self._a_x_b3(start, end, radius, wrap)
|
|
205
|
+
return _radians2m(x, radius)
|
|
206
|
+
|
|
207
|
+
def destination(self, distance, bearing, radius=R_M, height=None):
|
|
208
|
+
'''Locate the destination from this point after having
|
|
209
|
+
travelled the given distance on the given initial bearing.
|
|
210
|
+
|
|
211
|
+
@arg distance: Distance travelled (C{meter}, same units as
|
|
212
|
+
B{C{radius}}).
|
|
213
|
+
@arg bearing: Bearing from this point (compass C{degrees360}).
|
|
214
|
+
@kwarg radius: Mean earth radius (C{meter}).
|
|
215
|
+
@kwarg height: Optional height at destination (C{meter}, same
|
|
216
|
+
units a B{C{radius}}).
|
|
217
|
+
|
|
218
|
+
@return: Destination point (L{LatLon}).
|
|
219
|
+
|
|
220
|
+
@raise ValueError: Invalid B{C{distance}}, B{C{bearing}},
|
|
221
|
+
B{C{radius}} or B{C{height}}.
|
|
222
|
+
'''
|
|
223
|
+
a, b = self.philam
|
|
224
|
+
r, t = _m2radians(distance, radius, low=None), Bearing_(bearing)
|
|
225
|
+
|
|
226
|
+
a, b = _destination2(a, b, r, t)
|
|
227
|
+
h = self._heigHt(height)
|
|
228
|
+
return self.classof(degrees90(a), degrees180(b), height=h)
|
|
229
|
+
|
|
230
|
+
def distanceTo(self, other, radius=R_M, wrap=False):
|
|
231
|
+
'''Compute the (angular) distance from this to an other point.
|
|
232
|
+
|
|
233
|
+
@arg other: The other point (L{LatLon}).
|
|
234
|
+
@kwarg radius: Mean earth radius (C{meter}) or C{None}.
|
|
235
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
236
|
+
the B{C{other}} point (C{bool}).
|
|
237
|
+
|
|
238
|
+
@return: Distance between this and the B{C{other}} point
|
|
239
|
+
(C{meter}, same units as B{C{radius}} or
|
|
240
|
+
C{radians} if B{C{radius}} is C{None}).
|
|
241
|
+
|
|
242
|
+
@raise TypeError: The B{C{other}} point is not L{LatLon}.
|
|
243
|
+
|
|
244
|
+
@raise ValueError: Invalid B{C{radius}}.
|
|
245
|
+
'''
|
|
246
|
+
a1, _, a2, _, db = self._ab1_ab2_db5(other, wrap)
|
|
247
|
+
return _radians2m(vincentys_(a2, a1, db), radius)
|
|
248
|
+
|
|
249
|
+
# @Property_RO
|
|
250
|
+
# def Ecef(self):
|
|
251
|
+
# '''Get the ECEF I{class} (L{EcefVeness}), I{lazily}.
|
|
252
|
+
# '''
|
|
253
|
+
# return _MODS.ecef.EcefKarney
|
|
254
|
+
|
|
255
|
+
def greatCircle(self, bearing, Vector=Vector3d, **Vector_kwds):
|
|
256
|
+
'''Compute the vector normal to great circle obtained by heading
|
|
257
|
+
on the given initial bearing from this point.
|
|
258
|
+
|
|
259
|
+
Direction of vector is such that initial bearing vector
|
|
260
|
+
b = c × n, where n is an n-vector representing this point.
|
|
261
|
+
|
|
262
|
+
@arg bearing: Bearing from this point (compass C{degrees360}).
|
|
263
|
+
@kwarg Vector: Vector class to return the great circle,
|
|
264
|
+
overriding the default L{Vector3d}.
|
|
265
|
+
@kwarg Vector_kwds: Optional, additional keyword argunents
|
|
266
|
+
for B{C{Vector}}.
|
|
267
|
+
|
|
268
|
+
@return: Vector representing great circle (C{Vector}).
|
|
269
|
+
|
|
270
|
+
@raise ValueError: Invalid B{C{bearing}}.
|
|
271
|
+
'''
|
|
272
|
+
a, b = self.philam
|
|
273
|
+
sa, ca, sb, cb, st, ct = sincos2_(a, b, Bearing_(bearing))
|
|
274
|
+
|
|
275
|
+
return Vector(sb * ct - cb * sa * st,
|
|
276
|
+
-cb * ct - sb * sa * st,
|
|
277
|
+
ca * st, **Vector_kwds) # XXX .unit()?
|
|
278
|
+
|
|
279
|
+
def initialBearingTo(self, other, wrap=False, raiser=False):
|
|
280
|
+
'''Compute the initial bearing (forward azimuth) from this
|
|
281
|
+
to an other point.
|
|
282
|
+
|
|
283
|
+
@arg other: The other point (spherical L{LatLon}).
|
|
284
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
285
|
+
the B{C{other}} point (C{bool}).
|
|
286
|
+
@kwarg raiser: Optionally, raise L{CrossError} (C{bool}),
|
|
287
|
+
use C{B{raiser}=True} for behavior like
|
|
288
|
+
C{sphericalNvector.LatLon.initialBearingTo}.
|
|
289
|
+
|
|
290
|
+
@return: Initial bearing (compass C{degrees360}).
|
|
291
|
+
|
|
292
|
+
@raise CrossError: If this and the B{C{other}} point coincide,
|
|
293
|
+
provided both B{C{raiser}} is C{True} and
|
|
294
|
+
L{pygeodesy.crosserrors} is C{True}.
|
|
295
|
+
|
|
296
|
+
@raise TypeError: The B{C{other}} point is not L{LatLon}.
|
|
297
|
+
'''
|
|
298
|
+
a1, b1, a2, b2, db = self._ab1_ab2_db5(other, wrap)
|
|
299
|
+
# XXX behavior like sphericalNvector.LatLon.initialBearingTo
|
|
300
|
+
if raiser and crosserrors() and max(fabs(a2 - a1), fabs(db)) < EPS:
|
|
301
|
+
raise CrossError(_point_, self, other=other, wrap=wrap, txt=_coincident_)
|
|
302
|
+
|
|
303
|
+
return degrees(bearing_(a1, b1, a2, b2, final=False))
|
|
304
|
+
|
|
305
|
+
def intermediateTo(self, other, fraction, height=None, wrap=False):
|
|
306
|
+
'''Locate the point at given fraction between (or along) this
|
|
307
|
+
and an other point.
|
|
308
|
+
|
|
309
|
+
@arg other: The other point (L{LatLon}).
|
|
310
|
+
@arg fraction: Fraction between both points (C{scalar},
|
|
311
|
+
0.0 at this and 1.0 at the other point).
|
|
312
|
+
@kwarg height: Optional height, overriding the intermediate
|
|
313
|
+
height (C{meter}).
|
|
314
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
|
|
315
|
+
B{C{other}} point (C{bool}).
|
|
316
|
+
|
|
317
|
+
@return: Intermediate point (L{LatLon}).
|
|
318
|
+
|
|
319
|
+
@raise TypeError: The B{C{other}} point is not L{LatLon}.
|
|
320
|
+
|
|
321
|
+
@raise ValueError: Invalid B{C{fraction}} or B{C{height}}.
|
|
322
|
+
|
|
323
|
+
@see: Methods C{midpointTo} and C{rhumbMidpointTo}.
|
|
324
|
+
'''
|
|
325
|
+
p = self
|
|
326
|
+
f = Scalar(fraction=fraction)
|
|
327
|
+
if not isnear0(f):
|
|
328
|
+
p = p.others(other)
|
|
329
|
+
if wrap:
|
|
330
|
+
p = _Wrap.point(p)
|
|
331
|
+
if not isnear1(f): # and not near0
|
|
332
|
+
a1, b1 = self.philam
|
|
333
|
+
a2, b2 = p.philam
|
|
334
|
+
db, b2 = unrollPI(b1, b2, wrap=wrap)
|
|
335
|
+
r = vincentys_(a2, a1, db)
|
|
336
|
+
sr = sin(r)
|
|
337
|
+
if isnon0(sr):
|
|
338
|
+
sa1, ca1, sa2, ca2, \
|
|
339
|
+
sb1, cb1, sb2, cb2 = sincos2_(a1, a2, b1, b2)
|
|
340
|
+
|
|
341
|
+
t = f * r
|
|
342
|
+
a = sin(r - t) # / sr superflous
|
|
343
|
+
b = sin( t) # / sr superflous
|
|
344
|
+
|
|
345
|
+
x = a * ca1 * cb1 + b * ca2 * cb2
|
|
346
|
+
y = a * ca1 * sb1 + b * ca2 * sb2
|
|
347
|
+
z = a * sa1 + b * sa2
|
|
348
|
+
|
|
349
|
+
a = atan1d(z, hypot(x, y))
|
|
350
|
+
b = atan2d(y, x)
|
|
351
|
+
|
|
352
|
+
else: # PYCHOK no cover
|
|
353
|
+
a = degrees90( favg(a1, a2, f=f)) # coincident
|
|
354
|
+
b = degrees180(favg(b1, b2, f=f))
|
|
355
|
+
|
|
356
|
+
h = self._havg(other, f=f, h=height)
|
|
357
|
+
p = self.classof(a, b, height=h)
|
|
358
|
+
return p
|
|
359
|
+
|
|
360
|
+
def intersection(self, end1, other, end2, height=None, wrap=False):
|
|
361
|
+
'''Compute the intersection point of two lines, each defined by
|
|
362
|
+
two points or a start point and bearing from North.
|
|
363
|
+
|
|
364
|
+
@arg end1: End point of this line (L{LatLon}) or the initial
|
|
365
|
+
bearing at this point (compass C{degrees360}).
|
|
366
|
+
@arg other: Start point of the other line (L{LatLon}).
|
|
367
|
+
@arg end2: End point of the other line (L{LatLon}) or the
|
|
368
|
+
initial bearing at the B{C{other}} point (compass
|
|
369
|
+
C{degrees360}).
|
|
370
|
+
@kwarg height: Optional height for intersection point,
|
|
371
|
+
overriding the mean height (C{meter}).
|
|
372
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
373
|
+
B{C{start2}} and both B{C{end*}} points (C{bool}).
|
|
374
|
+
|
|
375
|
+
@return: The intersection point (L{LatLon}). An alternate
|
|
376
|
+
intersection point might be the L{antipode} to
|
|
377
|
+
the returned result.
|
|
378
|
+
|
|
379
|
+
@raise IntersectionError: Ambiguous or infinite intersection
|
|
380
|
+
or colinear, parallel or otherwise
|
|
381
|
+
non-intersecting lines.
|
|
382
|
+
|
|
383
|
+
@raise TypeError: If B{C{other}} is not L{LatLon} or B{C{end1}}
|
|
384
|
+
or B{C{end2}} not C{scalar} nor L{LatLon}.
|
|
385
|
+
|
|
386
|
+
@raise ValueError: Invalid B{C{height}} or C{null} line.
|
|
387
|
+
'''
|
|
388
|
+
try:
|
|
389
|
+
s2 = self.others(other)
|
|
390
|
+
return _intersect(self, end1, s2, end2, height=height, wrap=wrap,
|
|
391
|
+
LatLon=self.classof)
|
|
392
|
+
except (TypeError, ValueError) as x:
|
|
393
|
+
raise _xError(x, start1=self, end1=end1,
|
|
394
|
+
other=other, end2=end2, wrap=wrap)
|
|
395
|
+
|
|
396
|
+
def intersections2(self, rad1, other, rad2, radius=R_M, eps=_0_0,
|
|
397
|
+
height=None, wrap=True):
|
|
398
|
+
'''Compute the intersection points of two circles, each defined
|
|
399
|
+
by a center point and radius.
|
|
400
|
+
|
|
401
|
+
@arg rad1: Radius of the this circle (C{meter} or C{radians},
|
|
402
|
+
see B{C{radius}}).
|
|
403
|
+
@arg other: Center point of the other circle (L{LatLon}).
|
|
404
|
+
@arg rad2: Radius of the other circle (C{meter} or C{radians},
|
|
405
|
+
see B{C{radius}}).
|
|
406
|
+
@kwarg radius: Mean earth radius (C{meter} or C{None} if B{C{rad1}},
|
|
407
|
+
B{C{rad2}} and B{C{eps}} are given in C{radians}).
|
|
408
|
+
@kwarg eps: Required overlap (C{meter} or C{radians}, see
|
|
409
|
+
B{C{radius}}).
|
|
410
|
+
@kwarg height: Optional height for the intersection points (C{meter},
|
|
411
|
+
conventionally) or C{None} for the I{"radical height"}
|
|
412
|
+
at the I{radical line} between both centers.
|
|
413
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
|
|
414
|
+
B{C{other}} point (C{bool}).
|
|
415
|
+
|
|
416
|
+
@return: 2-Tuple of the intersection points, each a L{LatLon}
|
|
417
|
+
instance. For abutting circles, both intersection
|
|
418
|
+
points are the same instance, aka the I{radical center}.
|
|
419
|
+
|
|
420
|
+
@raise IntersectionError: Concentric, antipodal, invalid or
|
|
421
|
+
non-intersecting circles.
|
|
422
|
+
|
|
423
|
+
@raise TypeError: If B{C{other}} is not L{LatLon}.
|
|
424
|
+
|
|
425
|
+
@raise ValueError: Invalid B{C{rad1}}, B{C{rad2}}, B{C{radius}},
|
|
426
|
+
B{C{eps}} or B{C{height}}.
|
|
427
|
+
'''
|
|
428
|
+
try:
|
|
429
|
+
c2 = self.others(other)
|
|
430
|
+
return _intersects2(self, rad1, c2, rad2, radius=radius, eps=eps,
|
|
431
|
+
height=height, wrap=wrap,
|
|
432
|
+
LatLon=self.classof)
|
|
433
|
+
except (TypeError, ValueError) as x:
|
|
434
|
+
raise _xError(x, center=self, rad1=rad1,
|
|
435
|
+
other=other, rad2=rad2, wrap=wrap)
|
|
436
|
+
|
|
437
|
+
@deprecated_method
|
|
438
|
+
def isEnclosedBy(self, points): # PYCHOK no cover
|
|
439
|
+
'''DEPRECATED, use method C{isenclosedBy}.'''
|
|
440
|
+
return self.isenclosedBy(points)
|
|
441
|
+
|
|
442
|
+
def isenclosedBy(self, points, wrap=False):
|
|
443
|
+
'''Check whether a (convex) polygon or composite encloses this point.
|
|
444
|
+
|
|
445
|
+
@arg points: The polygon points or composite (L{LatLon}[],
|
|
446
|
+
L{BooleanFHP} or L{BooleanGH}).
|
|
447
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
|
|
448
|
+
B{C{points}} (C{bool}).
|
|
449
|
+
|
|
450
|
+
@return: C{True} if this point is inside the polygon or
|
|
451
|
+
composite, C{False} otherwise.
|
|
452
|
+
|
|
453
|
+
@raise PointsError: Insufficient number of B{C{points}}.
|
|
454
|
+
|
|
455
|
+
@raise TypeError: Some B{C{points}} are not L{LatLon}.
|
|
456
|
+
|
|
457
|
+
@raise ValueError: Invalid B{C{points}}, non-convex polygon.
|
|
458
|
+
|
|
459
|
+
@see: Functions L{pygeodesy.isconvex}, L{pygeodesy.isenclosedBy}
|
|
460
|
+
and L{pygeodesy.ispolar} especially if the B{C{points}} may
|
|
461
|
+
enclose a pole or wrap around the earth I{longitudinally}.
|
|
462
|
+
'''
|
|
463
|
+
if _MODS.booleans.isBoolean(points):
|
|
464
|
+
return points._encloses(self.lat, self.lon, wrap=wrap)
|
|
465
|
+
|
|
466
|
+
Ps = self.PointsIter(points, loop=2, dedup=True, wrap=wrap)
|
|
467
|
+
n0 = self._N_vector
|
|
468
|
+
|
|
469
|
+
v2 = Ps[0]._N_vector
|
|
470
|
+
p1 = Ps[1]
|
|
471
|
+
v1 = p1._N_vector
|
|
472
|
+
# check whether this point on same side of all
|
|
473
|
+
# polygon edges (to the left or right depending
|
|
474
|
+
# on the anti-/clockwise polygon direction)
|
|
475
|
+
gc1 = v2.cross(v1)
|
|
476
|
+
t0 = gc1.angleTo(n0) > PI_2
|
|
477
|
+
s0 = None
|
|
478
|
+
# get great-circle vector for each edge
|
|
479
|
+
for i, p2 in Ps.enumerate(closed=True):
|
|
480
|
+
if wrap and not Ps.looped:
|
|
481
|
+
p2 = _unrollon(p1, p2)
|
|
482
|
+
p1 = p2
|
|
483
|
+
v2 = p2._N_vector
|
|
484
|
+
gc = v1.cross(v2)
|
|
485
|
+
t = gc.angleTo(n0) > PI_2
|
|
486
|
+
if t != t0: # different sides of edge i
|
|
487
|
+
return False # outside
|
|
488
|
+
|
|
489
|
+
# check for convex polygon: angle between
|
|
490
|
+
# gc vectors, signed by direction of n0
|
|
491
|
+
# (otherwise the test above is not reliable)
|
|
492
|
+
s = signOf(gc1.angleTo(gc, vSign=n0))
|
|
493
|
+
if s != s0:
|
|
494
|
+
if s0 is None:
|
|
495
|
+
s0 = s
|
|
496
|
+
else:
|
|
497
|
+
t = _Fmt.SQUARE(points=i)
|
|
498
|
+
raise _ValueError(t, p2, wrap=wrap, txt=_not_(_convex_))
|
|
499
|
+
gc1, v1 = gc, v2
|
|
500
|
+
|
|
501
|
+
return True # inside
|
|
502
|
+
|
|
503
|
+
def midpointTo(self, other, height=None, fraction=_0_5, wrap=False):
|
|
504
|
+
'''Find the midpoint between this and an other point.
|
|
505
|
+
|
|
506
|
+
@arg other: The other point (L{LatLon}).
|
|
507
|
+
@kwarg height: Optional height for midpoint, overriding
|
|
508
|
+
the mean height (C{meter}).
|
|
509
|
+
@kwarg fraction: Midpoint location from this point (C{scalar}),
|
|
510
|
+
may be negative or greater than 1.0.
|
|
511
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
|
|
512
|
+
B{C{other}} point (C{bool}).
|
|
513
|
+
|
|
514
|
+
@return: Midpoint (L{LatLon}).
|
|
515
|
+
|
|
516
|
+
@raise TypeError: The B{C{other}} point is not L{LatLon}.
|
|
517
|
+
|
|
518
|
+
@raise ValueError: Invalid B{C{height}}.
|
|
519
|
+
|
|
520
|
+
@see: Methods C{intermediateTo} and C{rhumbMidpointTo}.
|
|
521
|
+
'''
|
|
522
|
+
if fraction is _0_5:
|
|
523
|
+
# see <https://MathForum.org/library/drmath/view/51822.html>
|
|
524
|
+
a1, b, a2, _, db = self._ab1_ab2_db5(other, wrap)
|
|
525
|
+
sa1, ca1, sa2, ca2, sdb, cdb = sincos2_(a1, a2, db)
|
|
526
|
+
|
|
527
|
+
x = ca2 * cdb + ca1
|
|
528
|
+
y = ca2 * sdb
|
|
529
|
+
|
|
530
|
+
a = atan1d(sa1 + sa2, hypot(x, y))
|
|
531
|
+
b = degrees180(b + atan2(y, x))
|
|
532
|
+
|
|
533
|
+
h = self._havg(other, h=height)
|
|
534
|
+
r = self.classof(a, b, height=h)
|
|
535
|
+
else:
|
|
536
|
+
r = self.intermediateTo(other, fraction, height=height, wrap=wrap)
|
|
537
|
+
return r
|
|
538
|
+
|
|
539
|
+
def nearestOn(self, point1, point2, radius=R_M, **wrap_adjust_limit):
|
|
540
|
+
'''Locate the point between two points closest to this point.
|
|
541
|
+
|
|
542
|
+
Distances are approximated by function L{pygeodesy.equirectangular_},
|
|
543
|
+
subject to the supplied B{C{options}}.
|
|
544
|
+
|
|
545
|
+
@arg point1: Start point (L{LatLon}).
|
|
546
|
+
@arg point2: End point (L{LatLon}).
|
|
547
|
+
@kwarg radius: Mean earth radius (C{meter}).
|
|
548
|
+
@kwarg wrap_adjust_limit: Optional keyword arguments for functions
|
|
549
|
+
L{sphericalTrigonometry.nearestOn3} and
|
|
550
|
+
L{pygeodesy.equirectangular_},
|
|
551
|
+
|
|
552
|
+
@return: Closest point on the great circle line (L{LatLon}).
|
|
553
|
+
|
|
554
|
+
@raise LimitError: Lat- and/or longitudinal delta exceeds B{C{limit}},
|
|
555
|
+
see function L{pygeodesy.equirectangular_}.
|
|
556
|
+
|
|
557
|
+
@raise NotImplementedError: Keyword argument C{B{within}=False}
|
|
558
|
+
is not (yet) supported.
|
|
559
|
+
|
|
560
|
+
@raise TypeError: Invalid B{C{point1}} or B{C{point2}}.
|
|
561
|
+
|
|
562
|
+
@raise ValueError: Invalid B{C{radius}} or B{C{options}}.
|
|
563
|
+
|
|
564
|
+
@see: Functions L{pygeodesy.equirectangular_} and L{pygeodesy.nearestOn5}
|
|
565
|
+
and method L{sphericalTrigonometry.LatLon.nearestOn3}.
|
|
566
|
+
'''
|
|
567
|
+
# remove kwarg B{C{within}} if present
|
|
568
|
+
w, kwds = _xkwds_pop2(wrap_adjust_limit, within=True)
|
|
569
|
+
if not w:
|
|
570
|
+
notImplemented(self, within=w)
|
|
571
|
+
|
|
572
|
+
# # UNTESTED - handle C{B{within}=False} and C{B{within}=True}
|
|
573
|
+
# wrap = _xkwds_get(options, wrap=False)
|
|
574
|
+
# a = self.alongTrackDistanceTo(point1, point2, radius=radius, wrap=wrap)
|
|
575
|
+
# if fabs(a) < EPS or (within and a < EPS):
|
|
576
|
+
# return point1
|
|
577
|
+
# d = point1.distanceTo(point2, radius=radius, wrap=wrap)
|
|
578
|
+
# if isnear0(d):
|
|
579
|
+
# return point1 # or point2
|
|
580
|
+
# elif fabs(d - a) < EPS or (a + EPS) > d:
|
|
581
|
+
# return point2
|
|
582
|
+
# f = a / d
|
|
583
|
+
# if within:
|
|
584
|
+
# if f > EPS1:
|
|
585
|
+
# return point2
|
|
586
|
+
# elif f < EPS:
|
|
587
|
+
# return point1
|
|
588
|
+
# return point1.intermediateTo(point2, f, wrap=wrap)
|
|
589
|
+
|
|
590
|
+
# without kwarg B{C{within}}, use backward compatible .nearestOn3
|
|
591
|
+
return self.nearestOn3([point1, point2], closed=False, radius=radius,
|
|
592
|
+
**kwds)[0]
|
|
593
|
+
|
|
594
|
+
@deprecated_method
|
|
595
|
+
def nearestOn2(self, points, closed=False, radius=R_M, **options): # PYCHOK no cover
|
|
596
|
+
'''DEPRECATED, use method L{sphericalTrigonometry.LatLon.nearestOn3}.
|
|
597
|
+
|
|
598
|
+
@return: ... 2-Tuple C{(closest, distance)} of the closest
|
|
599
|
+
point (L{LatLon}) on the polygon and the distance
|
|
600
|
+
to that point from this point in C{meter}, same
|
|
601
|
+
units of B{C{radius}}.
|
|
602
|
+
'''
|
|
603
|
+
r = self.nearestOn3(points, closed=closed, radius=radius, **options)
|
|
604
|
+
return r.closest, r.distance
|
|
605
|
+
|
|
606
|
+
def nearestOn3(self, points, closed=False, radius=R_M, **wrap_adjust_limit):
|
|
607
|
+
'''Locate the point on a polygon closest to this point.
|
|
608
|
+
|
|
609
|
+
Distances are approximated by function L{pygeodesy.equirectangular_},
|
|
610
|
+
subject to the supplied B{C{options}}.
|
|
611
|
+
|
|
612
|
+
@arg points: The polygon points (L{LatLon}[]).
|
|
613
|
+
@kwarg closed: Optionally, close the polygon (C{bool}).
|
|
614
|
+
@kwarg radius: Mean earth radius (C{meter}).
|
|
615
|
+
@kwarg wrap_adjust_limit: Optional keyword arguments for function
|
|
616
|
+
L{sphericalTrigonometry.nearestOn3} and
|
|
617
|
+
L{pygeodesy.equirectangular_},
|
|
618
|
+
|
|
619
|
+
@return: A L{NearestOn3Tuple}C{(closest, distance, angle)} of the
|
|
620
|
+
C{closest} point (L{LatLon}), the L{pygeodesy.equirectangular_}
|
|
621
|
+
C{distance} between this and the C{closest} point converted to
|
|
622
|
+
C{meter}, same units as B{C{radius}}. The C{angle} from this
|
|
623
|
+
to the C{closest} point is in compass C{degrees360}, like
|
|
624
|
+
function L{pygeodesy.compassAngle}.
|
|
625
|
+
|
|
626
|
+
@raise LimitError: Lat- and/or longitudinal delta exceeds B{C{limit}},
|
|
627
|
+
see function L{pygeodesy.equirectangular_}.
|
|
628
|
+
|
|
629
|
+
@raise PointsError: Insufficient number of B{C{points}}.
|
|
630
|
+
|
|
631
|
+
@raise TypeError: Some B{C{points}} are not C{LatLon}.
|
|
632
|
+
|
|
633
|
+
@raise ValueError: Invalid B{C{radius}} or B{C{options}}.
|
|
634
|
+
|
|
635
|
+
@see: Functions L{pygeodesy.compassAngle}, L{pygeodesy.equirectangular_}
|
|
636
|
+
and L{pygeodesy.nearestOn5}.
|
|
637
|
+
'''
|
|
638
|
+
return nearestOn3(self, points, closed=closed, radius=radius,
|
|
639
|
+
LatLon=self.classof, **wrap_adjust_limit)
|
|
640
|
+
|
|
641
|
+
def toCartesian(self, **Cartesian_datum_kwds): # PYCHOK Cartesian=Cartesian, datum=None
|
|
642
|
+
'''Convert this point to C{Karney}-based cartesian (ECEF)
|
|
643
|
+
coordinates.
|
|
644
|
+
|
|
645
|
+
@kwarg Cartesian_datum_kwds: Optional L{Cartesian}, B{C{datum}}
|
|
646
|
+
and other keyword arguments, ignored
|
|
647
|
+
if C{B{Cartesian} is None}. Use
|
|
648
|
+
C{B{Cartesian}=...} to override
|
|
649
|
+
this L{Cartesian} class or specify
|
|
650
|
+
C{B{Cartesian}=None}.
|
|
651
|
+
|
|
652
|
+
@return: The cartesian point (L{Cartesian}) or if B{C{Cartesian}}
|
|
653
|
+
is C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height,
|
|
654
|
+
C, M, datum)} with C{C} and C{M} if available.
|
|
655
|
+
|
|
656
|
+
@raise TypeError: Invalid B{C{Cartesian_datum_kwds}} argument.
|
|
657
|
+
'''
|
|
658
|
+
kwds = _xkwds(Cartesian_datum_kwds, Cartesian=Cartesian, datum=self.datum)
|
|
659
|
+
return LatLonSphericalBase.toCartesian(self, **kwds)
|
|
660
|
+
|
|
661
|
+
def triangle7(self, otherB, otherC, radius=R_M, wrap=False):
|
|
662
|
+
'''Compute the angles, sides and area of a spherical triangle.
|
|
663
|
+
|
|
664
|
+
@arg otherB: Second triangle point (C{LatLon}).
|
|
665
|
+
@arg otherC: Third triangle point (C{LatLon}).
|
|
666
|
+
@kwarg radius: Mean earth radius, ellipsoid or datum
|
|
667
|
+
(C{meter}, L{Ellipsoid}, L{Ellipsoid2},
|
|
668
|
+
L{Datum} or L{a_f2Tuple}) or C{None}.
|
|
669
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
|
|
670
|
+
B{C{otherB}} and B{C{otherC}} points (C{bool}).
|
|
671
|
+
|
|
672
|
+
@return: L{Triangle7Tuple}C{(A, a, B, b, C, c, area)} or if
|
|
673
|
+
B{C{radius}} is C{None}, a L{Triangle8Tuple}C{(A,
|
|
674
|
+
a, B, b, C, c, D, E)}.
|
|
675
|
+
|
|
676
|
+
@see: Function L{triangle7} and U{Spherical trigonometry
|
|
677
|
+
<https://WikiPedia.org/wiki/Spherical_trigonometry>}.
|
|
678
|
+
'''
|
|
679
|
+
B = self.others(otherB=otherB)
|
|
680
|
+
C = self.others(otherC=otherC)
|
|
681
|
+
B, C, _ = _unrollon3(self, B, C, wrap)
|
|
682
|
+
|
|
683
|
+
r = self.philam + B.philam + C.philam
|
|
684
|
+
t = triangle8_(*r, wrap=wrap)
|
|
685
|
+
return self._xnamed(_t7Tuple(t, radius))
|
|
686
|
+
|
|
687
|
+
def triangulate(self, bearing1, other, bearing2, **height_wrap):
|
|
688
|
+
'''Locate a point given this, an other point and the (initial) bearing
|
|
689
|
+
at this and at the other point.
|
|
690
|
+
|
|
691
|
+
@arg bearing1: Bearing at this point (compass C{degrees360}).
|
|
692
|
+
@arg other: The other point (C{LatLon}).
|
|
693
|
+
@arg bearing2: Bearing at the other point (compass C{degrees360}).
|
|
694
|
+
@kwarg height_wrap_tol: Optional keyword arguments C{B{height}=None},
|
|
695
|
+
C{B{wrap}=False}, see method L{intersection}.
|
|
696
|
+
|
|
697
|
+
@return: Triangulated point (C{LatLon}).
|
|
698
|
+
|
|
699
|
+
@see: Method L{intersection} for further details.
|
|
700
|
+
'''
|
|
701
|
+
if _isDegrees(bearing1) and _isDegrees(bearing2):
|
|
702
|
+
return self.intersection(bearing1, other, bearing2, **height_wrap)
|
|
703
|
+
raise _TypeError(bearing1=bearing1, bearing2=bearing2, **height_wrap)
|
|
704
|
+
|
|
705
|
+
def trilaterate5(self, distance1, point2, distance2, point3, distance3,
|
|
706
|
+
area=True, eps=EPS1, radius=R_M, wrap=False):
|
|
707
|
+
'''Trilaterate three points by I{area overlap} or I{perimeter
|
|
708
|
+
intersection} of three corresponding circles.
|
|
709
|
+
|
|
710
|
+
@arg distance1: Distance to this point (C{meter}, same units
|
|
711
|
+
as B{C{radius}}).
|
|
712
|
+
@arg point2: Second center point (C{LatLon}).
|
|
713
|
+
@arg distance2: Distance to point2 (C{meter}, same units as
|
|
714
|
+
B{C{radius}}).
|
|
715
|
+
@arg point3: Third center point (C{LatLon}).
|
|
716
|
+
@arg distance3: Distance to point3 (C{meter}, same units as
|
|
717
|
+
B{C{radius}}).
|
|
718
|
+
@kwarg area: If C{True} compute the area overlap, otherwise the
|
|
719
|
+
perimeter intersection of the circles (C{bool}).
|
|
720
|
+
@kwarg eps: The required I{minimal overlap} for C{B{area}=True}
|
|
721
|
+
or the I{intersection margin} for C{B{area}=False}
|
|
722
|
+
(C{meter}, same units as B{C{radius}}).
|
|
723
|
+
@kwarg radius: Mean earth radius (C{meter}, conventionally).
|
|
724
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
725
|
+
B{C{point2}} and B{C{point3}} (C{bool}).
|
|
726
|
+
|
|
727
|
+
@return: A L{Trilaterate5Tuple}C{(min, minPoint, max, maxPoint, n)}
|
|
728
|
+
with C{min} and C{max} in C{meter}, same units as B{C{eps}},
|
|
729
|
+
the corresponding trilaterated points C{minPoint} and
|
|
730
|
+
C{maxPoint} as I{spherical} C{LatLon} and C{n}, the number
|
|
731
|
+
of trilatered points found for the given B{C{eps}}.
|
|
732
|
+
|
|
733
|
+
If only a single trilaterated point is found, C{min I{is}
|
|
734
|
+
max}, C{minPoint I{is} maxPoint} and C{n = 1}.
|
|
735
|
+
|
|
736
|
+
For C{B{area}=True}, C{min} and C{max} are the smallest
|
|
737
|
+
respectively largest I{radial} overlap found.
|
|
738
|
+
|
|
739
|
+
For C{B{area}=False}, C{min} and C{max} represent the
|
|
740
|
+
nearest respectively farthest intersection margin.
|
|
741
|
+
|
|
742
|
+
If C{B{area}=True} and all 3 circles are concentric, C{n =
|
|
743
|
+
0} and C{minPoint} and C{maxPoint} are both the B{C{point#}}
|
|
744
|
+
with the smallest B{C{distance#}} C{min} and C{max} the
|
|
745
|
+
largest B{C{distance#}}.
|
|
746
|
+
|
|
747
|
+
@raise IntersectionError: Trilateration failed for the given B{C{eps}},
|
|
748
|
+
insufficient overlap for C{B{area}=True} or
|
|
749
|
+
no intersection or all (near-)concentric for
|
|
750
|
+
C{B{area}=False}.
|
|
751
|
+
|
|
752
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
753
|
+
|
|
754
|
+
@raise ValueError: Coincident B{C{point2}} or B{C{point3}} or invalid
|
|
755
|
+
B{C{distance1}}, B{C{distance2}}, B{C{distance3}}
|
|
756
|
+
or B{C{radius}}.
|
|
757
|
+
'''
|
|
758
|
+
return _trilaterate5(self, distance1,
|
|
759
|
+
self.others(point2=point2), distance2,
|
|
760
|
+
self.others(point3=point3), distance3,
|
|
761
|
+
area=area, radius=radius, eps=eps, wrap=wrap)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
_T00 = LatLon(0, 0, name='T00') # reference instance (L{LatLon})
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def areaOf(points, radius=R_M, wrap=False): # was=True
|
|
768
|
+
'''Calculate the area of a (spherical) polygon or composite
|
|
769
|
+
(with the pointsjoined by great circle arcs).
|
|
770
|
+
|
|
771
|
+
@arg points: The polygon points or clips (L{LatLon}[], L{BooleanFHP}
|
|
772
|
+
or L{BooleanGH}).
|
|
773
|
+
@kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},
|
|
774
|
+
L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple})
|
|
775
|
+
or C{None}.
|
|
776
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{points}}
|
|
777
|
+
(C{bool}).
|
|
778
|
+
|
|
779
|
+
@return: Polygon area (C{meter} I{quared}, same units as B{C{radius}}
|
|
780
|
+
or C{radians} if B{C{radius}} is C{None}).
|
|
781
|
+
|
|
782
|
+
@raise PointsError: Insufficient number of B{C{points}}.
|
|
783
|
+
|
|
784
|
+
@raise TypeError: Some B{C{points}} are not L{LatLon}.
|
|
785
|
+
|
|
786
|
+
@raise ValueError: Invalid B{C{radius}} or semi-circular polygon edge.
|
|
787
|
+
|
|
788
|
+
@note: The area is based on I{Karney}'s U{'Area of a spherical
|
|
789
|
+
polygon'<https://MathOverflow.net/questions/97711/
|
|
790
|
+
the-area-of-spherical-polygons>}, 3rd Answer.
|
|
791
|
+
|
|
792
|
+
@see: Functions L{pygeodesy.areaOf}, L{sphericalNvector.areaOf},
|
|
793
|
+
L{pygeodesy.excessKarney}, L{ellipsoidalExact.areaOf} and
|
|
794
|
+
L{ellipsoidalKarney.areaOf}.
|
|
795
|
+
'''
|
|
796
|
+
if _MODS.booleans.isBoolean(points):
|
|
797
|
+
return points._sum2(LatLon, areaOf, radius=radius, wrap=wrap)
|
|
798
|
+
|
|
799
|
+
_at2, _t_2 = atan2, tan_2
|
|
800
|
+
_un, _w180 = unrollPI, wrap180
|
|
801
|
+
|
|
802
|
+
Ps = _T00.PointsIter(points, loop=1, wrap=wrap)
|
|
803
|
+
p1 = p2 = Ps[0]
|
|
804
|
+
a1, b1 = p1.philam
|
|
805
|
+
ta1, z1 = _t_2(a1), None
|
|
806
|
+
|
|
807
|
+
A = Fsum() # mean phi
|
|
808
|
+
R = Fsum() # see L{pygeodesy.excessKarney_}
|
|
809
|
+
# ispolar: Summation of course deltas around pole is 0° rather than normally ±360°
|
|
810
|
+
# <https://blog.Element84.com/determining-if-a-spherical-polygon-contains-a-pole.html>
|
|
811
|
+
# XXX duplicate of function C{points.ispolar} to avoid copying all iterated points
|
|
812
|
+
D = Fsum()
|
|
813
|
+
for i, p2 in Ps.enumerate(closed=True):
|
|
814
|
+
a2, b2 = p2.philam
|
|
815
|
+
db, b2 = _un(b1, b2, wrap=wrap and not Ps.looped)
|
|
816
|
+
A += a2
|
|
817
|
+
ta2 = _t_2(a2)
|
|
818
|
+
tdb = _t_2(db, points=i)
|
|
819
|
+
R += _at2(tdb * (ta1 + ta2),
|
|
820
|
+
_1_0 + ta1 * ta2)
|
|
821
|
+
ta1, b1 = ta2, b2
|
|
822
|
+
|
|
823
|
+
if not p2.isequalTo(p1, eps=EPS):
|
|
824
|
+
z, z2 = _bearingTo2(p1, p2, wrap=wrap)
|
|
825
|
+
if z1 is not None:
|
|
826
|
+
D += _w180(z - z1) # (z - z1 + 540) ...
|
|
827
|
+
D += _w180(z2 - z) # (z2 - z + 540) % 360 - 180
|
|
828
|
+
p1, z1 = p2, z2
|
|
829
|
+
|
|
830
|
+
R = abs(R * _2_0)
|
|
831
|
+
if abs(D) < _90_0: # ispolar(points)
|
|
832
|
+
R = abs(R - PI2)
|
|
833
|
+
if radius:
|
|
834
|
+
a = degrees(A.fover(len(A))) # mean lat
|
|
835
|
+
R *= _mean_radius(radius, a)**2
|
|
836
|
+
return float(R)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def _destination2(a, b, r, t):
|
|
840
|
+
'''(INTERNAL) Destination lat- and longitude in C{radians}.
|
|
841
|
+
|
|
842
|
+
@arg a: Latitude (C{radians}).
|
|
843
|
+
@arg b: Longitude (C{radians}).
|
|
844
|
+
@arg r: Angular distance (C{radians}).
|
|
845
|
+
@arg t: Bearing (compass C{radians}).
|
|
846
|
+
|
|
847
|
+
@return: 2-Tuple (phi, lam) of (C{radians}, C{radiansPI}).
|
|
848
|
+
'''
|
|
849
|
+
# see <https://www.EdWilliams.org/avform.htm#LL>
|
|
850
|
+
sa, ca, sr, cr, st, ct = sincos2_(a, r, t)
|
|
851
|
+
ca *= sr
|
|
852
|
+
|
|
853
|
+
a = asin1(ct * ca + cr * sa)
|
|
854
|
+
d = atan2(st * ca, cr - sa * sin(a))
|
|
855
|
+
# note, in EdWilliams.org/avform.htm W is + and E is -
|
|
856
|
+
return a, (b + d) # (mod(b + d + PI, PI2) - PI)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def _int3d2(s, end, wrap, _i_, Vector, hs):
|
|
860
|
+
# see <https://www.EdWilliams.org/intersect.htm> (5) ff
|
|
861
|
+
# and similar logic in .ellipsoidalBaseDI._intersect3
|
|
862
|
+
a1, b1 = s.philam
|
|
863
|
+
|
|
864
|
+
if _isDegrees(end): # bearing, get pseudo-end point
|
|
865
|
+
a2, b2 = _destination2(a1, b1, PI_4, radians(end))
|
|
866
|
+
else: # must be a point
|
|
867
|
+
s.others(end, name=_end_ + _i_)
|
|
868
|
+
hs.append(end.height)
|
|
869
|
+
a2, b2 = end.philam
|
|
870
|
+
if wrap:
|
|
871
|
+
a2, b2 = _Wrap.philam(a2, b2)
|
|
872
|
+
|
|
873
|
+
db, b2 = unrollPI(b1, b2, wrap=wrap)
|
|
874
|
+
if max(fabs(db), fabs(a2 - a1)) < EPS:
|
|
875
|
+
raise _ValueError(_SPACE_(_line_ + _i_, _null_))
|
|
876
|
+
# note, in EdWilliams.org/avform.htm W is + and E is -
|
|
877
|
+
sb21, cb21, sb12, cb12 = sincos2_(db * _0_5,
|
|
878
|
+
-(b1 + b2) * _0_5)
|
|
879
|
+
cb21 *= sin(a1 - a2) # sa21
|
|
880
|
+
sb21 *= sin(a1 + a2) # sa12
|
|
881
|
+
x = Vector(sb12 * cb21 - cb12 * sb21,
|
|
882
|
+
cb12 * cb21 + sb12 * sb21,
|
|
883
|
+
cos(a1) * cos(a2) * sin(db)) # ll=start
|
|
884
|
+
return x.unit(), (db, (a2 - a1)) # negated d
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
def _intdot(ds, a1, b1, a, b, wrap):
|
|
888
|
+
# compute dot product ds . (-b + b1, a - a1)
|
|
889
|
+
db, _ = unrollPI(b1, b, wrap=wrap)
|
|
890
|
+
return fdot(ds, db, a - a1)
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
def intersecant2(center, circle, point, other, **radius_exact_height_wrap):
|
|
894
|
+
'''Compute the intersections of a circle and a (great circle) line given as
|
|
895
|
+
two points or as a point and bearing.
|
|
896
|
+
|
|
897
|
+
@arg center: Center of the circle (L{LatLon}).
|
|
898
|
+
@arg circle: Radius of the circle (C{meter}, same units as B{C{radius}})
|
|
899
|
+
or a point on the circle (L{LatLon}).
|
|
900
|
+
@arg point: A point on the (great circle) line (L{LatLon}).
|
|
901
|
+
@arg other: An other point on the (great circle) line (L{LatLon}) or
|
|
902
|
+
the bearing at the B{C{point}} (compass C{degrees360}).
|
|
903
|
+
@kwarg radius_exact_height_wrap: Optional keyword arguments, see
|
|
904
|
+
method L{LatLon.intersecant2} for further details.
|
|
905
|
+
|
|
906
|
+
@return: 2-Tuple of the intersection points (representing a chord), each
|
|
907
|
+
an instance of the B{C{point}} class. Both points are the same
|
|
908
|
+
instance if the (great circle) line is tangent to the circle.
|
|
909
|
+
|
|
910
|
+
@raise IntersectionError: The circle and line do not intersect.
|
|
911
|
+
|
|
912
|
+
@raise TypeError: If B{C{center}} or B{C{point}} not L{LatLon} or
|
|
913
|
+
B{C{circle}} or B{C{other}} invalid.
|
|
914
|
+
|
|
915
|
+
@raise UnitError: Invalid B{C{circle}}, B{C{other}}, B{C{radius}},
|
|
916
|
+
B{C{exact}}, B{C{height}} or B{C{napieradius}}.
|
|
917
|
+
'''
|
|
918
|
+
c = _T00.others(center=center)
|
|
919
|
+
p = _T00.others(point=point)
|
|
920
|
+
try:
|
|
921
|
+
return _intersecant2(c, circle, p, other, **radius_exact_height_wrap)
|
|
922
|
+
except (TypeError, ValueError) as x:
|
|
923
|
+
raise _xError(x, center=center, circle=circle, point=point, other=other,
|
|
924
|
+
**radius_exact_height_wrap)
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
def _intersect(start1, end1, start2, end2, height=None, wrap=False, # in.ellipsoidalBaseDI._intersect3
|
|
928
|
+
LatLon=None, **LatLon_kwds):
|
|
929
|
+
# (INTERNAL) Intersect two (spherical) lines, see L{intersection}
|
|
930
|
+
# above, separated to allow callers to embellish any exceptions
|
|
931
|
+
|
|
932
|
+
s1, s2 = start1, start2
|
|
933
|
+
if wrap:
|
|
934
|
+
s2 = _Wrap.point(s2)
|
|
935
|
+
hs = [s1.height, s2.height]
|
|
936
|
+
|
|
937
|
+
a1, b1 = s1.philam
|
|
938
|
+
a2, b2 = s2.philam
|
|
939
|
+
db, b2 = unrollPI(b1, b2, wrap=wrap)
|
|
940
|
+
r12 = vincentys_(a2, a1, db)
|
|
941
|
+
if fabs(r12) < EPS: # [nearly] coincident points
|
|
942
|
+
a, b = favg(a1, a2), favg(b1, b2)
|
|
943
|
+
|
|
944
|
+
# see <https://www.EdWilliams.org/avform.htm#Intersection>
|
|
945
|
+
elif _isDegrees(end1) and _isDegrees(end2): # both bearings
|
|
946
|
+
sa1, ca1, sa2, ca2, sr12, cr12 = sincos2_(a1, a2, r12)
|
|
947
|
+
|
|
948
|
+
x1, x2 = (sr12 * ca1), (sr12 * ca2)
|
|
949
|
+
if isnear0(x1) or isnear0(x2):
|
|
950
|
+
raise IntersectionError(_parallel_)
|
|
951
|
+
# handle domain error for equivalent longitudes,
|
|
952
|
+
# see also functions asin_safe and acos_safe at
|
|
953
|
+
# <https://www.EdWilliams.org/avform.htm#Math>
|
|
954
|
+
t12, t13 = acos1((sa2 - sa1 * cr12) / x1), radiansPI2(end1)
|
|
955
|
+
t21, t23 = acos1((sa1 - sa2 * cr12) / x2), radiansPI2(end2)
|
|
956
|
+
if sin(db) > 0:
|
|
957
|
+
t21 = PI2 - t21
|
|
958
|
+
else:
|
|
959
|
+
t12 = PI2 - t12
|
|
960
|
+
sx1, cx1, sx2, cx2 = sincos2_(wrapPI(t13 - t12), # angle 2-1-3
|
|
961
|
+
wrapPI(t21 - t23)) # angle 1-2-3)
|
|
962
|
+
if isnear0(sx1) and isnear0(sx2):
|
|
963
|
+
raise IntersectionError(_infinite_)
|
|
964
|
+
sx3 = sx1 * sx2
|
|
965
|
+
# XXX if sx3 < 0:
|
|
966
|
+
# XXX raise ValueError(_ambiguous_)
|
|
967
|
+
x3 = acos1(cr12 * sx3 - cx2 * cx1)
|
|
968
|
+
r13 = atan2(sr12 * sx3, cx2 + cx1 * cos(x3))
|
|
969
|
+
|
|
970
|
+
a, b = _destination2(a1, b1, r13, t13)
|
|
971
|
+
# like .ellipsoidalBaseDI,_intersect3, if this intersection
|
|
972
|
+
# is "before" the first point, use the antipodal intersection
|
|
973
|
+
if opposing_(t13, bearing_(a1, b1, a, b, wrap=wrap)):
|
|
974
|
+
a, b = antipode_(a, b) # PYCHOK PhiLam2Tuple
|
|
975
|
+
|
|
976
|
+
else: # end point(s) or bearing(s)
|
|
977
|
+
_N_vector_ = _MODS.nvectorBase._N_vector_
|
|
978
|
+
|
|
979
|
+
x1, d1 = _int3d2(s1, end1, wrap, _1_, _N_vector_, hs)
|
|
980
|
+
x2, d2 = _int3d2(s2, end2, wrap, _2_, _N_vector_, hs)
|
|
981
|
+
x = x1.cross(x2)
|
|
982
|
+
if x.length < EPS: # [nearly] colinear or parallel lines
|
|
983
|
+
raise IntersectionError(_colinear_)
|
|
984
|
+
a, b = x.philam
|
|
985
|
+
# choose intersection similar to sphericalNvector
|
|
986
|
+
if not (_intdot(d1, a1, b1, a, b, wrap) *
|
|
987
|
+
_intdot(d2, a2, b2, a, b, wrap)) > 0:
|
|
988
|
+
a, b = antipode_(a, b) # PYCHOK PhiLam2Tuple
|
|
989
|
+
|
|
990
|
+
h = fmean(hs) if height is None else Height(height)
|
|
991
|
+
return _LL3Tuple(degrees90(a), degrees180(b), h,
|
|
992
|
+
intersection, LatLon, LatLon_kwds)
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
def intersection(start1, end1, start2, end2, height=None, wrap=False,
|
|
996
|
+
LatLon=LatLon, **LatLon_kwds):
|
|
997
|
+
'''Compute the intersection point of two lines, each defined
|
|
998
|
+
by two points or a start point and bearing from North.
|
|
999
|
+
|
|
1000
|
+
@arg start1: Start point of the first line (L{LatLon}).
|
|
1001
|
+
@arg end1: End point of the first line (L{LatLon}) or
|
|
1002
|
+
the initial bearing at the first start point
|
|
1003
|
+
(compass C{degrees360}).
|
|
1004
|
+
@arg start2: Start point of the second line (L{LatLon}).
|
|
1005
|
+
@arg end2: End point of the second line (L{LatLon}) or
|
|
1006
|
+
the initial bearing at the second start point
|
|
1007
|
+
(compass C{degrees360}).
|
|
1008
|
+
@kwarg height: Optional height for the intersection point,
|
|
1009
|
+
overriding the mean height (C{meter}).
|
|
1010
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
1011
|
+
B{C{start2}} and both B{C{end*}} points (C{bool}).
|
|
1012
|
+
@kwarg LatLon: Optional class to return the intersection
|
|
1013
|
+
point (L{LatLon}) or C{None}.
|
|
1014
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
|
|
1015
|
+
arguments, ignored if C{B{LatLon} is None}.
|
|
1016
|
+
|
|
1017
|
+
@return: The intersection point as a (B{C{LatLon}}) or if
|
|
1018
|
+
C{B{LatLon} is None} a L{LatLon3Tuple}C{(lat, lon,
|
|
1019
|
+
height)}. An alternate intersection point might
|
|
1020
|
+
be the L{antipode} to the returned result.
|
|
1021
|
+
|
|
1022
|
+
@raise IntersectionError: Ambiguous or infinite intersection
|
|
1023
|
+
or colinear, parallel or otherwise
|
|
1024
|
+
non-intersecting lines.
|
|
1025
|
+
|
|
1026
|
+
@raise TypeError: A B{C{start1}}, B{C{end1}}, B{C{start2}}
|
|
1027
|
+
or B{C{end2}} point not L{LatLon}.
|
|
1028
|
+
|
|
1029
|
+
@raise ValueError: Invalid B{C{height}} or C{null} line.
|
|
1030
|
+
'''
|
|
1031
|
+
s1 = _T00.others(start1=start1)
|
|
1032
|
+
s2 = _T00.others(start2=start2)
|
|
1033
|
+
try:
|
|
1034
|
+
return _intersect(s1, end1, s2, end2, height=height, wrap=wrap,
|
|
1035
|
+
LatLon=LatLon, **LatLon_kwds)
|
|
1036
|
+
except (TypeError, ValueError) as x:
|
|
1037
|
+
raise _xError(x, start1=start1, end1=end1, start2=start2, end2=end2)
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
def intersections2(center1, rad1, center2, rad2, radius=R_M, eps=_0_0,
|
|
1041
|
+
height=None, wrap=False, # was=True
|
|
1042
|
+
LatLon=LatLon, **LatLon_kwds):
|
|
1043
|
+
'''Compute the intersection points of two circles each defined
|
|
1044
|
+
by a center point and a radius.
|
|
1045
|
+
|
|
1046
|
+
@arg center1: Center of the first circle (L{LatLon}).
|
|
1047
|
+
@arg rad1: Radius of the first circle (C{meter} or C{radians},
|
|
1048
|
+
see B{C{radius}}).
|
|
1049
|
+
@arg center2: Center of the second circle (L{LatLon}).
|
|
1050
|
+
@arg rad2: Radius of the second circle (C{meter} or C{radians},
|
|
1051
|
+
see B{C{radius}}).
|
|
1052
|
+
@kwarg radius: Mean earth radius (C{meter} or C{None} if B{C{rad1}},
|
|
1053
|
+
B{C{rad2}} and B{C{eps}} are given in C{radians}).
|
|
1054
|
+
@kwarg eps: Required overlap (C{meter} or C{radians}, see
|
|
1055
|
+
B{C{radius}}).
|
|
1056
|
+
@kwarg height: Optional height for the intersection points (C{meter},
|
|
1057
|
+
conventionally) or C{None} for the I{"radical height"}
|
|
1058
|
+
at the I{radical line} between both centers.
|
|
1059
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{center2}}
|
|
1060
|
+
(C{bool}).
|
|
1061
|
+
@kwarg LatLon: Optional class to return the intersection
|
|
1062
|
+
points (L{LatLon}) or C{None}.
|
|
1063
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
|
|
1064
|
+
arguments, ignored if C{B{LatLon} is None}.
|
|
1065
|
+
|
|
1066
|
+
@return: 2-Tuple of the intersection points, each a B{C{LatLon}}
|
|
1067
|
+
instance or if C{B{LatLon} is None} a L{LatLon3Tuple}C{(lat,
|
|
1068
|
+
lon, height)}. For abutting circles, both intersection
|
|
1069
|
+
points are the same instance, aka the I{radical center}.
|
|
1070
|
+
|
|
1071
|
+
@raise IntersectionError: Concentric, antipodal, invalid or
|
|
1072
|
+
non-intersecting circles.
|
|
1073
|
+
|
|
1074
|
+
@raise TypeError: If B{C{center1}} or B{C{center2}} not L{LatLon}.
|
|
1075
|
+
|
|
1076
|
+
@raise ValueError: Invalid B{C{rad1}}, B{C{rad2}}, B{C{radius}},
|
|
1077
|
+
B{C{eps}} or B{C{height}}.
|
|
1078
|
+
|
|
1079
|
+
@note: Courtesy of U{Samuel Čavoj<https://GitHub.com/mrJean1/PyGeodesy/issues/41>}.
|
|
1080
|
+
|
|
1081
|
+
@see: This U{Answer<https://StackOverflow.com/questions/53324667/
|
|
1082
|
+
find-intersection-coordinates-of-two-circles-on-earth/53331953>}.
|
|
1083
|
+
'''
|
|
1084
|
+
c1 = _T00.others(center1=center1)
|
|
1085
|
+
c2 = _T00.others(center2=center2)
|
|
1086
|
+
try:
|
|
1087
|
+
return _intersects2(c1, rad1, c2, rad2, radius=radius, eps=eps,
|
|
1088
|
+
height=height, wrap=wrap,
|
|
1089
|
+
LatLon=LatLon, **LatLon_kwds)
|
|
1090
|
+
except (TypeError, ValueError) as x:
|
|
1091
|
+
raise _xError(x, center1=center1, rad1=rad1,
|
|
1092
|
+
center2=center2, rad2=rad2, wrap=wrap)
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
def _intersects2(c1, rad1, c2, rad2, radius=R_M, eps=_0_0, # in .ellipsoidalBaseDI._intersects2
|
|
1096
|
+
height=None, too_d=None, wrap=False, # was=True
|
|
1097
|
+
LatLon=LatLon, **LatLon_kwds):
|
|
1098
|
+
# (INTERNAL) Intersect two spherical circles, see L{intersections2}
|
|
1099
|
+
# above, separated to allow callers to embellish any exceptions
|
|
1100
|
+
|
|
1101
|
+
def _dest3(bearing, h):
|
|
1102
|
+
a, b = _destination2(a1, b1, r1, bearing)
|
|
1103
|
+
return _LL3Tuple(degrees90(a), degrees180(b), h,
|
|
1104
|
+
intersections2, LatLon, LatLon_kwds)
|
|
1105
|
+
|
|
1106
|
+
a1, b1 = c1.philam
|
|
1107
|
+
a2, b2 = c2.philam
|
|
1108
|
+
if wrap:
|
|
1109
|
+
a2, b2 = _Wrap.philam(a2, b2)
|
|
1110
|
+
|
|
1111
|
+
r1, r2, f = _rads3(rad1, rad2, radius)
|
|
1112
|
+
if f: # swapped radii, swap centers
|
|
1113
|
+
a1, a2 = a2, a1 # PYCHOK swap!
|
|
1114
|
+
b1, b2 = b2, b1 # PYCHOK swap!
|
|
1115
|
+
|
|
1116
|
+
db, b2 = unrollPI(b1, b2, wrap=wrap)
|
|
1117
|
+
d = vincentys_(a2, a1, db) # radians
|
|
1118
|
+
if d < max(r1 - r2, EPS):
|
|
1119
|
+
raise IntersectionError(_near_(_concentric_)) # XXX ConcentricError?
|
|
1120
|
+
|
|
1121
|
+
r = eps if radius is None else (m2radians(
|
|
1122
|
+
eps, radius=radius) if eps else _0_0)
|
|
1123
|
+
if r < _0_0:
|
|
1124
|
+
raise _ValueError(eps=r)
|
|
1125
|
+
|
|
1126
|
+
x = fsumf_(r1, r2, -d) # overlap
|
|
1127
|
+
if x > max(r, EPS):
|
|
1128
|
+
sd, cd, sr1, cr1, _, cr2 = sincos2_(d, r1, r2)
|
|
1129
|
+
x = sd * sr1
|
|
1130
|
+
if isnear0(x):
|
|
1131
|
+
raise _ValueError(_invalid_)
|
|
1132
|
+
x = acos1((cr2 - cd * cr1) / x) # 0 <= x <= PI
|
|
1133
|
+
|
|
1134
|
+
elif x < r: # PYCHOK no cover
|
|
1135
|
+
t = (d * radius) if too_d is None else too_d
|
|
1136
|
+
raise IntersectionError(_too_(_Fmt.distant(t)))
|
|
1137
|
+
|
|
1138
|
+
if height is None: # "radical height"
|
|
1139
|
+
f = _radical2(d, r1, r2).ratio
|
|
1140
|
+
h = Height(favg(c1.height, c2.height, f=f))
|
|
1141
|
+
else:
|
|
1142
|
+
h = Height(height)
|
|
1143
|
+
|
|
1144
|
+
b = bearing_(a1, b1, a2, b2, final=False, wrap=wrap)
|
|
1145
|
+
if x < EPS4: # externally ...
|
|
1146
|
+
r = _dest3(b, h)
|
|
1147
|
+
elif x > _PI_EPS4: # internally ...
|
|
1148
|
+
r = _dest3(b + PI, h)
|
|
1149
|
+
else:
|
|
1150
|
+
return _dest3(b + x, h), _dest3(b - x, h)
|
|
1151
|
+
return r, r # ... abutting circles
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
@deprecated_function
|
|
1155
|
+
def isPoleEnclosedBy(points, wrap=False): # PYCHOK no cover
|
|
1156
|
+
'''DEPRECATED, use function L{pygeodesy.ispolar}.
|
|
1157
|
+
'''
|
|
1158
|
+
return ispolar(points, wrap=wrap)
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
def _LL3Tuple(lat, lon, height, where, LatLon, LatLon_kwds):
|
|
1162
|
+
'''(INTERNAL) Helper for L{intersection}, L{intersections2} and L{meanOf}.
|
|
1163
|
+
'''
|
|
1164
|
+
n = where.__name__
|
|
1165
|
+
if LatLon is None:
|
|
1166
|
+
r = LatLon3Tuple(lat, lon, height, name=n)
|
|
1167
|
+
else:
|
|
1168
|
+
kwds = _xkwds(LatLon_kwds, height=height, name=n)
|
|
1169
|
+
r = LatLon(lat, lon, **kwds)
|
|
1170
|
+
return r
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
def meanOf(points, height=None, wrap=False, LatLon=LatLon, **LatLon_kwds):
|
|
1174
|
+
'''Compute the I{geographic} mean of several points.
|
|
1175
|
+
|
|
1176
|
+
@arg points: Points to be averaged (L{LatLon}[]).
|
|
1177
|
+
@kwarg height: Optional height at mean point, overriding the mean
|
|
1178
|
+
height (C{meter}).
|
|
1179
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} the B{C{points}}
|
|
1180
|
+
(C{bool}).
|
|
1181
|
+
@kwarg LatLon: Optional class to return the mean point (L{LatLon})
|
|
1182
|
+
or C{None}.
|
|
1183
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
|
|
1184
|
+
arguments, ignored if C{B{LatLon} is None}.
|
|
1185
|
+
|
|
1186
|
+
@return: The geographic mean and height (B{C{LatLon}}) or a
|
|
1187
|
+
L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}}
|
|
1188
|
+
is C{None}.
|
|
1189
|
+
|
|
1190
|
+
@raise TypeError: Some B{C{points}} are not L{LatLon}.
|
|
1191
|
+
|
|
1192
|
+
@raise ValueError: No B{C{points}} or invalid B{C{height}}.
|
|
1193
|
+
'''
|
|
1194
|
+
def _N_vs(ps, w):
|
|
1195
|
+
Ps = _T00.PointsIter(ps, wrap=w)
|
|
1196
|
+
for p in Ps.iterate(closed=False):
|
|
1197
|
+
yield p._N_vector
|
|
1198
|
+
|
|
1199
|
+
m = _MODS.nvectorBase
|
|
1200
|
+
# geographic, vectorial mean
|
|
1201
|
+
n = m.sumOf(_N_vs(points, wrap), h=height, Vector=m.NvectorBase)
|
|
1202
|
+
lat, lon, h = n.latlonheight
|
|
1203
|
+
return _LL3Tuple(lat, lon, h, meanOf, LatLon, LatLon_kwds)
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
@deprecated_function
|
|
1207
|
+
def nearestOn2(point, points, **closed_radius_LatLon_options): # PYCHOK no cover
|
|
1208
|
+
'''DEPRECATED, use function L{sphericalTrigonometry.nearestOn3}.
|
|
1209
|
+
|
|
1210
|
+
@return: ... 2-tuple C{(closest, distance)} of the C{closest}
|
|
1211
|
+
point (L{LatLon}) on the polygon and the C{distance}
|
|
1212
|
+
between the C{closest} and the given B{C{point}}. The
|
|
1213
|
+
C{closest} is a B{C{LatLon}} or a L{LatLon2Tuple}C{(lat,
|
|
1214
|
+
lon)} if B{C{LatLon}} is C{None} ...
|
|
1215
|
+
'''
|
|
1216
|
+
ll, d, _ = nearestOn3(point, points, **closed_radius_LatLon_options) # PYCHOK 3-tuple
|
|
1217
|
+
if _xkwds_get(closed_radius_LatLon_options, LatLon=LatLon) is None:
|
|
1218
|
+
ll = LatLon2Tuple(ll.lat, ll.lon)
|
|
1219
|
+
return ll, d
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
def nearestOn3(point, points, closed=False, radius=R_M, wrap=False, adjust=True,
|
|
1223
|
+
limit=9, **LatLon_and_kwds):
|
|
1224
|
+
'''Locate the point on a path or polygon closest to a reference point.
|
|
1225
|
+
|
|
1226
|
+
Distances are I{approximated} using function L{pygeodesy.equirectangular_},
|
|
1227
|
+
subject to the supplied B{C{options}}.
|
|
1228
|
+
|
|
1229
|
+
@arg point: The reference point (L{LatLon}).
|
|
1230
|
+
@arg points: The path or polygon points (L{LatLon}[]).
|
|
1231
|
+
@kwarg closed: Optionally, close the polygon (C{bool}).
|
|
1232
|
+
@kwarg radius: Mean earth radius (C{meter}).
|
|
1233
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
|
|
1234
|
+
B{C{points}} (C{bool}).
|
|
1235
|
+
@kwarg adjust: See function L{pygeodesy.equirectangular_} (C{bool}).
|
|
1236
|
+
@kwarg limit: See function L{pygeodesy.equirectangular_} (C{degrees}),
|
|
1237
|
+
default C{9 degrees} is about C{1,000 Kmeter} (for mean
|
|
1238
|
+
spherical earth radius L{R_KM}).
|
|
1239
|
+
@kwarg LatLon: Optional class to return the closest point (L{LatLon})
|
|
1240
|
+
or C{None}.
|
|
1241
|
+
@kwarg options: Optional keyword arguments for function
|
|
1242
|
+
L{pygeodesy.equirectangular_}.
|
|
1243
|
+
|
|
1244
|
+
@return: A L{NearestOn3Tuple}C{(closest, distance, angle)} with the
|
|
1245
|
+
C{closest} point as B{C{LatLon}} or L{LatLon3Tuple}C{(lat,
|
|
1246
|
+
lon, height)} if B{C{LatLon}} is C{None}. The C{distance}
|
|
1247
|
+
is the L{pygeodesy.equirectangular_} distance between the
|
|
1248
|
+
C{closest} and the given B{C{point}} converted to C{meter},
|
|
1249
|
+
same units as B{C{radius}}. The C{angle} from the given
|
|
1250
|
+
B{C{point}} to the C{closest} is in compass C{degrees360},
|
|
1251
|
+
like function L{pygeodesy.compassAngle}. The C{height} is
|
|
1252
|
+
the (interpolated) height at the C{closest} point.
|
|
1253
|
+
|
|
1254
|
+
@raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}},
|
|
1255
|
+
see function L{pygeodesy.equirectangular_}.
|
|
1256
|
+
|
|
1257
|
+
@raise PointsError: Insufficient number of B{C{points}}.
|
|
1258
|
+
|
|
1259
|
+
@raise TypeError: Some B{C{points}} are not C{LatLon}.
|
|
1260
|
+
|
|
1261
|
+
@raise ValueError: Invalid B{C{radius}}.
|
|
1262
|
+
|
|
1263
|
+
@see: Functions L{pygeodesy.equirectangular_} and L{pygeodesy.nearestOn5}.
|
|
1264
|
+
'''
|
|
1265
|
+
t = _nearestOn5(point, points, closed=closed, wrap=wrap,
|
|
1266
|
+
adjust=adjust, limit=limit)
|
|
1267
|
+
d = degrees2m(t.distance, radius=radius)
|
|
1268
|
+
h = t.height
|
|
1269
|
+
n = nearestOn3.__name__
|
|
1270
|
+
|
|
1271
|
+
LL, kwds = _xkwds_pop2(LatLon_and_kwds, LatLon=LatLon)
|
|
1272
|
+
r = LatLon3Tuple(t.lat, t.lon, h, name=n) if LL is None else \
|
|
1273
|
+
LL(t.lat, t.lon, **_xkwds(kwds, height=h, name=n))
|
|
1274
|
+
return NearestOn3Tuple(r, d, t.angle, name=n)
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
def perimeterOf(points, closed=False, radius=R_M, wrap=True):
|
|
1278
|
+
'''Compute the perimeter of a (spherical) polygon or composite
|
|
1279
|
+
(with great circle arcs joining the points).
|
|
1280
|
+
|
|
1281
|
+
@arg points: The polygon points or clips (L{LatLon}[], L{BooleanFHP}
|
|
1282
|
+
or L{BooleanGH}).
|
|
1283
|
+
@kwarg closed: Optionally, close the polygon (C{bool}).
|
|
1284
|
+
@kwarg radius: Mean earth radius (C{meter}) or C{None}.
|
|
1285
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
|
|
1286
|
+
B{C{points}} (C{bool}).
|
|
1287
|
+
|
|
1288
|
+
@return: Polygon perimeter (C{meter}, same units as B{C{radius}}
|
|
1289
|
+
or C{radians} if B{C{radius}} is C{None}).
|
|
1290
|
+
|
|
1291
|
+
@raise PointsError: Insufficient number of B{C{points}}.
|
|
1292
|
+
|
|
1293
|
+
@raise TypeError: Some B{C{points}} are not L{LatLon}.
|
|
1294
|
+
|
|
1295
|
+
@raise ValueError: Invalid B{C{radius}} or C{B{closed}=False} with
|
|
1296
|
+
C{B{points}} a composite.
|
|
1297
|
+
|
|
1298
|
+
@note: Distances are based on function L{pygeodesy.vincentys_}.
|
|
1299
|
+
|
|
1300
|
+
@see: Functions L{perimeterOf<pygeodesy.perimeterOf>},
|
|
1301
|
+
L{sphericalNvector.perimeterOf} and L{ellipsoidalKarney.perimeterOf}.
|
|
1302
|
+
'''
|
|
1303
|
+
def _rads(ps, c, w): # angular edge lengths in radians
|
|
1304
|
+
Ps = _T00.PointsIter(ps, loop=1, wrap=w)
|
|
1305
|
+
a1, b1 = Ps[0].philam
|
|
1306
|
+
for p in Ps.iterate(closed=c):
|
|
1307
|
+
a2, b2 = p.philam
|
|
1308
|
+
db, b2 = unrollPI(b1, b2, wrap=w and not (c and Ps.looped))
|
|
1309
|
+
yield vincentys_(a2, a1, db)
|
|
1310
|
+
a1, b1 = a2, b2
|
|
1311
|
+
|
|
1312
|
+
if _MODS.booleans.isBoolean(points):
|
|
1313
|
+
if not closed:
|
|
1314
|
+
raise _ValueError(closed=closed, points=_composite_)
|
|
1315
|
+
r = points._sum2(LatLon, perimeterOf, closed=True, radius=radius, wrap=wrap)
|
|
1316
|
+
else:
|
|
1317
|
+
r = fsum(_rads(points, closed, wrap), floats=True)
|
|
1318
|
+
return _radians2m(r, radius)
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
def triangle7(latA, lonA, latB, lonB, latC, lonC, radius=R_M,
|
|
1322
|
+
excess=excessAbc_,
|
|
1323
|
+
wrap=False):
|
|
1324
|
+
'''Compute the angles, sides, and area of a (spherical) triangle.
|
|
1325
|
+
|
|
1326
|
+
@arg latA: First corner latitude (C{degrees}).
|
|
1327
|
+
@arg lonA: First corner longitude (C{degrees}).
|
|
1328
|
+
@arg latB: Second corner latitude (C{degrees}).
|
|
1329
|
+
@arg lonB: Second corner longitude (C{degrees}).
|
|
1330
|
+
@arg latC: Third corner latitude (C{degrees}).
|
|
1331
|
+
@arg lonC: Third corner longitude (C{degrees}).
|
|
1332
|
+
@kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},
|
|
1333
|
+
L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple})
|
|
1334
|
+
or C{None}.
|
|
1335
|
+
@kwarg excess: I{Spherical excess} callable (L{excessAbc_},
|
|
1336
|
+
L{excessGirard_} or L{excessLHuilier_}).
|
|
1337
|
+
@kwarg wrap: If C{True}, wrap and L{pygeodesy.unroll180}
|
|
1338
|
+
longitudes (C{bool}).
|
|
1339
|
+
|
|
1340
|
+
@return: A L{Triangle7Tuple}C{(A, a, B, b, C, c, area)} with
|
|
1341
|
+
spherical angles C{A}, C{B} and C{C}, angular sides
|
|
1342
|
+
C{a}, C{b} and C{c} all in C{degrees} and C{area}
|
|
1343
|
+
in I{square} C{meter} or same units as B{C{radius}}
|
|
1344
|
+
I{squared} or if C{B{radius}=0} or C{None}, a
|
|
1345
|
+
L{Triangle8Tuple}C{(A, a, B, b, C, c, D, E)} all in
|
|
1346
|
+
C{radians} with the I{spherical excess} C{E} as the
|
|
1347
|
+
C{unit area} in C{radians}.
|
|
1348
|
+
'''
|
|
1349
|
+
t = triangle8_(Phi_(latA=latA), Lam_(lonA=lonA),
|
|
1350
|
+
Phi_(latB=latB), Lam_(lonB=lonB),
|
|
1351
|
+
Phi_(latC=latC), Lam_(lonC=lonC),
|
|
1352
|
+
excess=excess, wrap=wrap)
|
|
1353
|
+
return _t7Tuple(t, radius)
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
def triangle8_(phiA, lamA, phiB, lamB, phiC, lamC, excess=excessAbc_,
|
|
1357
|
+
wrap=False):
|
|
1358
|
+
'''Compute the angles, sides, I{spherical deficit} and I{spherical
|
|
1359
|
+
excess} of a (spherical) triangle.
|
|
1360
|
+
|
|
1361
|
+
@arg phiA: First corner latitude (C{radians}).
|
|
1362
|
+
@arg lamA: First corner longitude (C{radians}).
|
|
1363
|
+
@arg phiB: Second corner latitude (C{radians}).
|
|
1364
|
+
@arg lamB: Second corner longitude (C{radians}).
|
|
1365
|
+
@arg phiC: Third corner latitude (C{radians}).
|
|
1366
|
+
@arg lamC: Third corner longitude (C{radians}).
|
|
1367
|
+
@kwarg excess: I{Spherical excess} callable (L{excessAbc_},
|
|
1368
|
+
L{excessGirard_} or L{excessLHuilier_}).
|
|
1369
|
+
@kwarg wrap: If C{True}, L{pygeodesy.unrollPI} the
|
|
1370
|
+
longitudinal deltas (C{bool}).
|
|
1371
|
+
|
|
1372
|
+
@return: A L{Triangle8Tuple}C{(A, a, B, b, C, c, D, E)} with
|
|
1373
|
+
spherical angles C{A}, C{B} and C{C}, angular sides
|
|
1374
|
+
C{a}, C{b} and C{c}, I{spherical deficit} C{D} and
|
|
1375
|
+
I{spherical excess} C{E}, all in C{radians}.
|
|
1376
|
+
'''
|
|
1377
|
+
def _a_r(w, phiA, lamA, phiB, lamB, phiC, lamC):
|
|
1378
|
+
d, _ = unrollPI(lamB, lamC, wrap=w)
|
|
1379
|
+
a = vincentys_(phiC, phiB, d)
|
|
1380
|
+
return a, (phiB, lamB, phiC, lamC, phiA, lamA) # rotate A, B, C
|
|
1381
|
+
|
|
1382
|
+
def _A_r(a, sa, ca, sb, cb, sc, cc):
|
|
1383
|
+
s = sb * sc
|
|
1384
|
+
A = acos1((ca - cb * cc) / s) if isnon0(s) else a
|
|
1385
|
+
return A, (sb, cb, sc, cc, sa, ca) # rotate sincos2_'s
|
|
1386
|
+
|
|
1387
|
+
# notation: side C{a} is oposite to corner C{A}, etc.
|
|
1388
|
+
a, r = _a_r(wrap, phiA, lamA, phiB, lamB, phiC, lamC)
|
|
1389
|
+
b, r = _a_r(wrap, *r)
|
|
1390
|
+
c, _ = _a_r(wrap, *r)
|
|
1391
|
+
|
|
1392
|
+
A, r = _A_r(a, *sincos2_(a, b, c))
|
|
1393
|
+
B, r = _A_r(b, *r)
|
|
1394
|
+
C, _ = _A_r(c, *r)
|
|
1395
|
+
|
|
1396
|
+
D = fsumf_(PI2, -a, -b, -c) # deficit aka defect
|
|
1397
|
+
E = excessGirard_(A, B, C) if excess in (excessGirard_, True) else (
|
|
1398
|
+
excessLHuilier_(a, b, c) if excess in (excessLHuilier_, False) else
|
|
1399
|
+
excessAbc_(*max((A, b, c), (B, c, a), (C, a, b))))
|
|
1400
|
+
|
|
1401
|
+
return Triangle8Tuple(A, a, B, b, C, c, D, E)
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
def _t7Tuple(t, radius):
|
|
1405
|
+
'''(INTERNAL) Convert a L{Triangle8Tuple} to L{Triangle7Tuple}.
|
|
1406
|
+
'''
|
|
1407
|
+
if radius: # not in (None, _0_0)
|
|
1408
|
+
r = radius if _isRadius(radius) else \
|
|
1409
|
+
_ellipsoidal_datum(radius).ellipsoid.Rmean
|
|
1410
|
+
A, B, C = map1(degrees, t.A, t.B, t.C)
|
|
1411
|
+
t = Triangle7Tuple(A, (r * t.a),
|
|
1412
|
+
B, (r * t.b),
|
|
1413
|
+
C, (r * t.c), t.E * r**2)
|
|
1414
|
+
return t
|
|
1415
|
+
|
|
1416
|
+
|
|
1417
|
+
__all__ += _ALL_OTHER(Cartesian, LatLon, # classes
|
|
1418
|
+
areaOf, # functions
|
|
1419
|
+
intersecant2, intersection, intersections2, ispolar,
|
|
1420
|
+
isPoleEnclosedBy, # DEPRECATED, use ispolar
|
|
1421
|
+
meanOf,
|
|
1422
|
+
nearestOn2, nearestOn3,
|
|
1423
|
+
perimeterOf,
|
|
1424
|
+
sumOf, # XXX == vector3d.sumOf
|
|
1425
|
+
triangle7, triangle8_)
|
|
1426
|
+
|
|
1427
|
+
# **) MIT License
|
|
1428
|
+
#
|
|
1429
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
1430
|
+
#
|
|
1431
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
1432
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
1433
|
+
# to deal in the Software without restriction, including without limitation
|
|
1434
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
1435
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
1436
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
1437
|
+
#
|
|
1438
|
+
# The above copyright notice and this permission notice shall be included
|
|
1439
|
+
# in all copies or substantial portions of the Software.
|
|
1440
|
+
#
|
|
1441
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
1442
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1443
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
1444
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
1445
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
1446
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
1447
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|