pygeodesy 24.3.24__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PyGeodesy-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- pygeodesy/wgrs.py +439 -0
pygeodesy/latlonBase.py
ADDED
|
@@ -0,0 +1,1766 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''(INTERNAL) Base class L{LatLonBase} for all elliposiodal, spherical and N-vectorial C{LatLon} classes.
|
|
5
|
+
|
|
6
|
+
@see: I{(C) Chris Veness}' U{latlong<https://www.Movable-Type.co.UK/scripts/latlong.html>},
|
|
7
|
+
U{-ellipsoidal<https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html>} and
|
|
8
|
+
U{-vectors<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>} and I{Charles Karney}'s
|
|
9
|
+
U{Rhumb<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Rhumb.html>} and
|
|
10
|
+
U{RhumbLine<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1RhumbLine.html>} classes.
|
|
11
|
+
'''
|
|
12
|
+
|
|
13
|
+
from pygeodesy.basics import isstr, map1, _xinstanceof
|
|
14
|
+
from pygeodesy.constants import EPS, EPS0, EPS1, EPS4, INT0, R_M, \
|
|
15
|
+
_EPSqrt as _TOL, _0_0, _0_5, _1_0, \
|
|
16
|
+
_360_0, _umod_360
|
|
17
|
+
from pygeodesy.datums import _spherical_datum
|
|
18
|
+
from pygeodesy.dms import F_D, F_DMS, latDMS, lonDMS, parse3llh
|
|
19
|
+
# from pygeodesy.ecef import EcefKarney # _MODS
|
|
20
|
+
from pygeodesy.errors import _AttributeError, IntersectionError, \
|
|
21
|
+
_incompatible, _IsnotError, _TypeError, \
|
|
22
|
+
_ValueError, _xattr, _xdatum, _xError, \
|
|
23
|
+
_xkwds, _xkwds_item2, _xkwds_not
|
|
24
|
+
# from pygeodesy.fmath import favg # _MODS
|
|
25
|
+
# from pygeodesy.formy import antipode, compassAngle, cosineAndoyerLambert_, \
|
|
26
|
+
# cosineForsytheAndoyerLambert_, cosineLaw, \
|
|
27
|
+
# equirectangular, euclidean, flatLocal_, \
|
|
28
|
+
# flatPolar, _hartzell, haversine, isantipode, \
|
|
29
|
+
# _isequalTo, isnormal, normal, philam2n_xyz, \
|
|
30
|
+
# thomas_, vincentys # as _formy
|
|
31
|
+
from pygeodesy.interns import NN, _COMMASPACE_, _concentric_, _height_, \
|
|
32
|
+
_intersection_, _LatLon_, _m_, _negative_, \
|
|
33
|
+
_no_, _overlap_, _too_, _point_ # PYCHOK used!
|
|
34
|
+
# from pygeodesy.iters import PointsIter, points2 # from .vector3d, _MODS
|
|
35
|
+
# from pygeodesy.karney import Caps # _MODS
|
|
36
|
+
from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
|
|
37
|
+
# from pygeodesy.ltp import Ltp, _xLtp # _MODS
|
|
38
|
+
from pygeodesy.named import _NamedBase, notImplemented, notOverloaded, Fmt
|
|
39
|
+
from pygeodesy.namedTuples import Bounds2Tuple, LatLon2Tuple, PhiLam2Tuple, \
|
|
40
|
+
Trilaterate5Tuple
|
|
41
|
+
# from pygeodesy.nvectorBase import _N_vector_ # _MODS
|
|
42
|
+
from pygeodesy.props import deprecated_method, Property, Property_RO, \
|
|
43
|
+
property_RO, _update_all
|
|
44
|
+
# from pygeodesy.streprs import Fmt, hstr # from .named, _MODS
|
|
45
|
+
from pygeodesy.units import _isDegrees, _isRadius, Distance_, Lat, Lon, \
|
|
46
|
+
Height, Radius, Radius_, Scalar, Scalar_
|
|
47
|
+
from pygeodesy.utily import _unrollon, _unrollon3, _Wrap
|
|
48
|
+
from pygeodesy.vector2d import _circin6, Circin6Tuple, _circum3, circum4_, \
|
|
49
|
+
Circum3Tuple, _radii11ABC
|
|
50
|
+
from pygeodesy.vector3d import nearestOn6, Vector3d, PointsIter
|
|
51
|
+
|
|
52
|
+
from contextlib import contextmanager
|
|
53
|
+
from math import asin, cos, degrees, fabs, radians
|
|
54
|
+
|
|
55
|
+
__all__ = _ALL_LAZY.latlonBase
|
|
56
|
+
__version__ = '24.03.15'
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LatLonBase(_NamedBase):
|
|
60
|
+
'''(INTERNAL) Base class for C{LatLon} points on spherical or
|
|
61
|
+
ellipsoidal earth models.
|
|
62
|
+
'''
|
|
63
|
+
_clipid = INT0 # polygonal clip, see .booleans
|
|
64
|
+
_datum = None # L{Datum}, to be overriden
|
|
65
|
+
_height = INT0 # height (C{meter}), default
|
|
66
|
+
_lat = 0 # latitude (C{degrees})
|
|
67
|
+
_lon = 0 # longitude (C{degrees})
|
|
68
|
+
|
|
69
|
+
def __init__(self, latlonh, lon=None, height=0, wrap=False, name=NN, datum=None):
|
|
70
|
+
'''New C{LatLon}.
|
|
71
|
+
|
|
72
|
+
@arg latlonh: Latitude (C{degrees} or DMS C{str} with N or S suffix) or
|
|
73
|
+
a previous C{LatLon} instance provided C{B{lon}=None}.
|
|
74
|
+
@kwarg lon: Longitude (C{degrees} or DMS C{str} with E or W suffix) or
|
|
75
|
+
C(None), indicating B{C{latlonh}} is a C{LatLon}.
|
|
76
|
+
@kwarg height: Optional height above (or below) the earth surface
|
|
77
|
+
(C{meter}, conventionally).
|
|
78
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} B{C{lat}} and B{C{lon}}
|
|
79
|
+
(C{bool}).
|
|
80
|
+
@kwarg name: Optional name (C{str}).
|
|
81
|
+
@kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2},
|
|
82
|
+
L{a_f2Tuple} or I{scalar} radius) or C{None}.
|
|
83
|
+
|
|
84
|
+
@return: New instance (C{LatLon}).
|
|
85
|
+
|
|
86
|
+
@raise RangeError: A B{C{lon}} or C{lat} value outside the valid
|
|
87
|
+
range and L{rangerrors} set to C{True}.
|
|
88
|
+
|
|
89
|
+
@raise TypeError: If B{C{latlonh}} is not a C{LatLon}.
|
|
90
|
+
|
|
91
|
+
@raise UnitError: Invalid B{C{lat}}, B{C{lon}} or B{C{height}}.
|
|
92
|
+
'''
|
|
93
|
+
if name:
|
|
94
|
+
self.name = name
|
|
95
|
+
|
|
96
|
+
if lon is None:
|
|
97
|
+
lat, lon, height = _latlonheight3(latlonh, height, wrap)
|
|
98
|
+
elif wrap:
|
|
99
|
+
lat, lon = _Wrap.latlonDMS2(latlonh, lon)
|
|
100
|
+
else:
|
|
101
|
+
lat = latlonh
|
|
102
|
+
|
|
103
|
+
self._lat = Lat(lat) # parseDMS2(lat, lon)
|
|
104
|
+
self._lon = Lon(lon) # PYCHOK LatLon2Tuple
|
|
105
|
+
if height: # elevation
|
|
106
|
+
self._height = Height(height)
|
|
107
|
+
if datum is not None:
|
|
108
|
+
self._datum = _spherical_datum(datum, name=self.name)
|
|
109
|
+
|
|
110
|
+
def __eq__(self, other):
|
|
111
|
+
return self.isequalTo(other)
|
|
112
|
+
|
|
113
|
+
def __ne__(self, other):
|
|
114
|
+
return not self.isequalTo(other)
|
|
115
|
+
|
|
116
|
+
def __str__(self):
|
|
117
|
+
return self.toStr(form=F_D, prec=6)
|
|
118
|
+
|
|
119
|
+
def antipode(self, height=None):
|
|
120
|
+
'''Return the antipode, the point diametrically opposite to
|
|
121
|
+
this point.
|
|
122
|
+
|
|
123
|
+
@kwarg height: Optional height of the antipode (C{meter}),
|
|
124
|
+
this point's height otherwise.
|
|
125
|
+
|
|
126
|
+
@return: The antipodal point (C{LatLon}).
|
|
127
|
+
'''
|
|
128
|
+
a = self._formy.antipode(*self.latlon)
|
|
129
|
+
h = self._heigHt(height)
|
|
130
|
+
return self.classof(*a, height=h)
|
|
131
|
+
|
|
132
|
+
@deprecated_method
|
|
133
|
+
def bounds(self, wide, tall, radius=R_M): # PYCHOK no cover
|
|
134
|
+
'''DEPRECATED, use method C{boundsOf}.'''
|
|
135
|
+
return self.boundsOf(wide, tall, radius=radius)
|
|
136
|
+
|
|
137
|
+
def boundsOf(self, wide, tall, radius=R_M, height=None):
|
|
138
|
+
'''Return the SW and NE lat-/longitude of a great circle
|
|
139
|
+
bounding box centered at this location.
|
|
140
|
+
|
|
141
|
+
@arg wide: Longitudinal box width (C{meter}, same units as
|
|
142
|
+
B{C{radius}} or C{degrees} if B{C{radius}} is C{None}).
|
|
143
|
+
@arg tall: Latitudinal box size (C{meter}, same units as
|
|
144
|
+
B{C{radius}} or C{degrees} if B{C{radius}} is C{None}).
|
|
145
|
+
@kwarg radius: Mean earth radius (C{meter}) or C{None} if I{both}
|
|
146
|
+
B{C{wide}} and B{C{tall}} are in C{degrees}.
|
|
147
|
+
@kwarg height: Height for C{latlonSW} and C{latlonNE} (C{meter}),
|
|
148
|
+
overriding the point's height.
|
|
149
|
+
|
|
150
|
+
@return: A L{Bounds2Tuple}C{(latlonSW, latlonNE)}, the
|
|
151
|
+
lower-left and upper-right corner (C{LatLon}).
|
|
152
|
+
|
|
153
|
+
@see: U{https://www.Movable-Type.co.UK/scripts/latlong-db.html}
|
|
154
|
+
'''
|
|
155
|
+
w = Scalar_(wide=wide) * _0_5
|
|
156
|
+
t = Scalar_(tall=tall) * _0_5
|
|
157
|
+
if radius is not None:
|
|
158
|
+
r = Radius_(radius)
|
|
159
|
+
c = cos(self.phi)
|
|
160
|
+
w = degrees(asin(w / r) / c) if fabs(c) > EPS0 else _0_0 # XXX
|
|
161
|
+
t = degrees(t / r)
|
|
162
|
+
y, t = self.lat, fabs(t)
|
|
163
|
+
x, w = self.lon, fabs(w)
|
|
164
|
+
|
|
165
|
+
h = self._heigHt(height)
|
|
166
|
+
sw = self.classof(y - t, x - w, height=h)
|
|
167
|
+
ne = self.classof(y + t, x + w, height=h)
|
|
168
|
+
return Bounds2Tuple(sw, ne, name=self.name)
|
|
169
|
+
|
|
170
|
+
def chordTo(self, other, height=None, wrap=False):
|
|
171
|
+
'''Compute the length of the chord through the earth between
|
|
172
|
+
this and an other point.
|
|
173
|
+
|
|
174
|
+
@arg other: The other point (C{LatLon}).
|
|
175
|
+
@kwarg height: Overriding height for both points (C{meter})
|
|
176
|
+
or C{None} for each point's height.
|
|
177
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} the B{C{other}}
|
|
178
|
+
point (C{bool}).
|
|
179
|
+
|
|
180
|
+
@return: The chord length (conventionally C{meter}).
|
|
181
|
+
|
|
182
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
183
|
+
'''
|
|
184
|
+
def _v3d(ll):
|
|
185
|
+
t = ll.toEcef(height=height) # .toVector(Vector=Vector3d)
|
|
186
|
+
return Vector3d(t.x, t.y, t.z)
|
|
187
|
+
|
|
188
|
+
p = self.others(other)
|
|
189
|
+
if wrap:
|
|
190
|
+
p = _Wrap.point(p)
|
|
191
|
+
return _v3d(self).minus(_v3d(p)).length
|
|
192
|
+
|
|
193
|
+
def circin6(self, point2, point3, eps=EPS4, wrap=False):
|
|
194
|
+
'''Return the radius and center of the I{inscribed} aka I{In-}circle
|
|
195
|
+
of the (planar) triangle formed by this and two other points.
|
|
196
|
+
|
|
197
|
+
@arg point2: Second point (C{LatLon}).
|
|
198
|
+
@arg point3: Third point (C{LatLon}).
|
|
199
|
+
@kwarg eps: Tolerance for function L{pygeodesy.trilaterate3d2}.
|
|
200
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} B{C{point2}} and
|
|
201
|
+
B{C{point3}} (C{bool}).
|
|
202
|
+
|
|
203
|
+
@return: L{Circin6Tuple}C{(radius, center, deltas, cA, cB, cC)}. The
|
|
204
|
+
C{center} and contact points C{cA}, C{cB} and C{cC}, each an
|
|
205
|
+
instance of this (sub-)class, are co-planar with this and the
|
|
206
|
+
two given points, see the B{Note} below.
|
|
207
|
+
|
|
208
|
+
@raise ImportError: Package C{numpy} not found, not installed or older
|
|
209
|
+
than version 1.10.
|
|
210
|
+
|
|
211
|
+
@raise IntersectionError: Near-coincident or -colinear points or
|
|
212
|
+
a trilateration or C{numpy} issue.
|
|
213
|
+
|
|
214
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
215
|
+
|
|
216
|
+
@note: The C{center} is trilaterated in cartesian (ECEF) space and converted
|
|
217
|
+
back to geodetic lat-, longitude and height. The latter, conventionally
|
|
218
|
+
in C{meter} indicates whether the C{center} is above, below or on the
|
|
219
|
+
surface of the earth model. If C{deltas} is C{None}, the C{center} is
|
|
220
|
+
I{un}ambigous. Otherwise C{deltas} is a L{LatLon3Tuple}C{(lat, lon,
|
|
221
|
+
height)} representing the differences between both results from
|
|
222
|
+
L{pygeodesy.trilaterate3d2} and C{center} is the mean thereof.
|
|
223
|
+
|
|
224
|
+
@see: Function L{pygeodesy.circin6}, method L{circum3}, U{Incircle
|
|
225
|
+
<https://MathWorld.Wolfram.com/Incircle.html>} and U{Contact Triangle
|
|
226
|
+
<https://MathWorld.Wolfram.com/ContactTriangle.html>}.
|
|
227
|
+
'''
|
|
228
|
+
with _toCartesian3(self, point2, point3, wrap) as cs:
|
|
229
|
+
r, c, d, cA, cB, cC = _circin6(*cs, eps=eps, useZ=True, dLL3=True,
|
|
230
|
+
datum=self.datum) # PYCHOK unpack
|
|
231
|
+
return Circin6Tuple(r, c.toLatLon(), d, cA.toLatLon(), cB.toLatLon(), cC.toLatLon())
|
|
232
|
+
|
|
233
|
+
def circum3(self, point2, point3, circum=True, eps=EPS4, wrap=False):
|
|
234
|
+
'''Return the radius and center of the smallest circle I{through} or I{containing}
|
|
235
|
+
this and two other points.
|
|
236
|
+
|
|
237
|
+
@arg point2: Second point (C{LatLon}).
|
|
238
|
+
@arg point3: Third point (C{LatLon}).
|
|
239
|
+
@kwarg circum: If C{True} return the C{circumradius} and C{circumcenter},
|
|
240
|
+
always, ignoring the I{Meeus}' Type I case (C{bool}).
|
|
241
|
+
@kwarg eps: Tolerance for function L{pygeodesy.trilaterate3d2}.
|
|
242
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} B{C{point2}} and
|
|
243
|
+
B{C{point3}} (C{bool}).
|
|
244
|
+
|
|
245
|
+
@return: A L{Circum3Tuple}C{(radius, center, deltas)}. The C{center}, an
|
|
246
|
+
instance of this (sub-)class, is co-planar with this and the two
|
|
247
|
+
given points. If C{deltas} is C{None}, the C{center} is
|
|
248
|
+
I{un}ambigous. Otherwise C{deltas} is a L{LatLon3Tuple}C{(lat,
|
|
249
|
+
lon, height)} representing the difference between both results
|
|
250
|
+
from L{pygeodesy.trilaterate3d2} and C{center} is the mean thereof.
|
|
251
|
+
|
|
252
|
+
@raise ImportError: Package C{numpy} not found, not installed or older than
|
|
253
|
+
version 1.10.
|
|
254
|
+
|
|
255
|
+
@raise IntersectionError: Near-concentric, -coincident or -colinear points,
|
|
256
|
+
incompatible C{Ecef} classes or a trilateration
|
|
257
|
+
or C{numpy} issue.
|
|
258
|
+
|
|
259
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
260
|
+
|
|
261
|
+
@note: The C{center} is trilaterated in cartesian (ECEF) space and converted
|
|
262
|
+
back to geodetic lat-, longitude and height. The latter, conventionally
|
|
263
|
+
in C{meter} indicates whether the C{center} is above, below or on the
|
|
264
|
+
surface of the earth model. If C{deltas} is C{None}, the C{center} is
|
|
265
|
+
I{un}ambigous. Otherwise C{deltas} is a L{LatLon3Tuple}C{(lat, lon,
|
|
266
|
+
height)} representing the difference between both results from
|
|
267
|
+
L{pygeodesy.trilaterate3d2} and C{center} is the mean thereof.
|
|
268
|
+
|
|
269
|
+
@see: Function L{pygeodesy.circum3} and methods L{circin6} and L{circum4_}.
|
|
270
|
+
'''
|
|
271
|
+
with _toCartesian3(self, point2, point3, wrap, circum=circum) as cs:
|
|
272
|
+
r, c, d = _circum3(*cs, circum=circum, eps=eps, useZ=True, dLL3=True, # XXX -3d2
|
|
273
|
+
clas=cs[0].classof, datum=self.datum) # PYCHOK unpack
|
|
274
|
+
return Circum3Tuple(r, c.toLatLon(), d)
|
|
275
|
+
|
|
276
|
+
def circum4_(self, *points, **wrap):
|
|
277
|
+
'''Best-fit a sphere through this and two or more other points.
|
|
278
|
+
|
|
279
|
+
@arg points: The other points (each a C{LatLon}).
|
|
280
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} the B{C{points}}
|
|
281
|
+
(C{bool}), default C{False}.
|
|
282
|
+
|
|
283
|
+
@return: L{Circum4Tuple}C{(radius, center, rank, residuals)} with C{center}
|
|
284
|
+
an instance of this (sub-)class.
|
|
285
|
+
|
|
286
|
+
@raise ImportError: Package C{numpy} not found, not installed or older than
|
|
287
|
+
version 1.10.
|
|
288
|
+
|
|
289
|
+
@raise NumPyError: Some C{numpy} issue.
|
|
290
|
+
|
|
291
|
+
@raise TypeError: One of the B{C{points}} invalid.
|
|
292
|
+
|
|
293
|
+
@raise ValueError: Too few B{C{points}}.
|
|
294
|
+
|
|
295
|
+
@see: Function L{pygeodesy.circum4_} and L{circum3}.
|
|
296
|
+
'''
|
|
297
|
+
def _cs(ps, C, wrap=False):
|
|
298
|
+
_wp = _Wrap.point if wrap else (lambda p: p)
|
|
299
|
+
for i, p in enumerate(ps):
|
|
300
|
+
yield C(i=i, points=_wp(p))
|
|
301
|
+
|
|
302
|
+
C = self._toCartesianEcef
|
|
303
|
+
c = C(point=self)
|
|
304
|
+
t = circum4_(c, Vector=c.classof, *_cs(points, C, **wrap))
|
|
305
|
+
c = t.center.toLatLon(LatLon=self.classof)
|
|
306
|
+
return t.dup(center=c)
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def clipid(self):
|
|
310
|
+
'''Get the (polygonal) clip (C{int}).
|
|
311
|
+
'''
|
|
312
|
+
return self._clipid
|
|
313
|
+
|
|
314
|
+
@clipid.setter # PYCHOK setter!
|
|
315
|
+
def clipid(self, clipid):
|
|
316
|
+
'''Get the (polygonal) clip (C{int}).
|
|
317
|
+
'''
|
|
318
|
+
self._clipid = int(clipid)
|
|
319
|
+
|
|
320
|
+
@deprecated_method
|
|
321
|
+
def compassAngle(self, other, **adjust_wrap): # PYCHOK no cover
|
|
322
|
+
'''DEPRECATED, use method L{compassAngleTo}.'''
|
|
323
|
+
return self.compassAngleTo(other, **adjust_wrap)
|
|
324
|
+
|
|
325
|
+
def compassAngleTo(self, other, **adjust_wrap):
|
|
326
|
+
'''Return the angle from North for the direction vector between
|
|
327
|
+
this and an other point.
|
|
328
|
+
|
|
329
|
+
Suitable only for short, non-near-polar vectors up to a few
|
|
330
|
+
hundred Km or Miles. Use method C{initialBearingTo} for
|
|
331
|
+
larger distances.
|
|
332
|
+
|
|
333
|
+
@arg other: The other point (C{LatLon}).
|
|
334
|
+
@kwarg adjust_wrap: Optional keyword arguments for function
|
|
335
|
+
L{pygeodesy.compassAngle}.
|
|
336
|
+
|
|
337
|
+
@return: Compass angle from North (C{degrees360}).
|
|
338
|
+
|
|
339
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
340
|
+
|
|
341
|
+
@note: Courtesy of Martin Schultz.
|
|
342
|
+
|
|
343
|
+
@see: U{Local, flat earth approximation
|
|
344
|
+
<https://www.EdWilliams.org/avform.htm#flat>}.
|
|
345
|
+
'''
|
|
346
|
+
p = self.others(other)
|
|
347
|
+
return self._formy.compassAngle(self.lat, self.lon, p.lat, p.lon, **adjust_wrap)
|
|
348
|
+
|
|
349
|
+
def cosineAndoyerLambertTo(self, other, wrap=False):
|
|
350
|
+
'''Compute the distance between this and an other point using the U{Andoyer-Lambert correction<https://
|
|
351
|
+
navlib.net/wp-content/uploads/2013/10/admiralty-manual-of-navigation-vol-1-1964-english501c.pdf>}
|
|
352
|
+
of the U{Law of Cosines<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>} formula.
|
|
353
|
+
|
|
354
|
+
@arg other: The other point (C{LatLon}).
|
|
355
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
356
|
+
the B{C{other}} point (C{bool}).
|
|
357
|
+
|
|
358
|
+
@return: Distance (C{meter}, same units as the axes of this
|
|
359
|
+
point's datum ellipsoid).
|
|
360
|
+
|
|
361
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
362
|
+
|
|
363
|
+
@see: Function L{pygeodesy.cosineAndoyerLambert} and methods
|
|
364
|
+
L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo},
|
|
365
|
+
C{distanceTo*}, L{equirectangularTo}, L{euclideanTo},
|
|
366
|
+
L{flatLocalTo}/L{hubenyTo}, L{flatPolarTo}, L{haversineTo},
|
|
367
|
+
L{thomasTo} and L{vincentysTo}.
|
|
368
|
+
'''
|
|
369
|
+
return self._distanceTo_(self._formy.cosineAndoyerLambert_, other, wrap=wrap)
|
|
370
|
+
|
|
371
|
+
def cosineForsytheAndoyerLambertTo(self, other, wrap=False):
|
|
372
|
+
'''Compute the distance between this and an other point using
|
|
373
|
+
the U{Forsythe-Andoyer-Lambert correction
|
|
374
|
+
<https://www2.UNB.Ca/gge/Pubs/TR77.pdf>} of the U{Law of Cosines
|
|
375
|
+
<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>}
|
|
376
|
+
formula.
|
|
377
|
+
|
|
378
|
+
@arg other: The other point (C{LatLon}).
|
|
379
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
380
|
+
the B{C{other}} point (C{bool}).
|
|
381
|
+
|
|
382
|
+
@return: Distance (C{meter}, same units as the axes of
|
|
383
|
+
this point's datum ellipsoid).
|
|
384
|
+
|
|
385
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
386
|
+
|
|
387
|
+
@see: Function L{pygeodesy.cosineForsytheAndoyerLambert} and methods
|
|
388
|
+
L{cosineAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
|
|
389
|
+
L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
|
|
390
|
+
L{flatPolarTo}, L{haversineTo}, L{thomasTo} and L{vincentysTo}.
|
|
391
|
+
'''
|
|
392
|
+
return self._distanceTo_(self._formy.cosineForsytheAndoyerLambert_, other, wrap=wrap)
|
|
393
|
+
|
|
394
|
+
def cosineLawTo(self, other, radius=None, wrap=False):
|
|
395
|
+
'''Compute the distance between this and an other point using the
|
|
396
|
+
U{spherical Law of Cosines
|
|
397
|
+
<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>}
|
|
398
|
+
formula.
|
|
399
|
+
|
|
400
|
+
@arg other: The other point (C{LatLon}).
|
|
401
|
+
@kwarg radius: Mean earth radius (C{meter}) or C{None}
|
|
402
|
+
for the mean radius of this point's datum
|
|
403
|
+
ellipsoid.
|
|
404
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
405
|
+
the B{C{other}} point (C{bool}).
|
|
406
|
+
|
|
407
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
408
|
+
|
|
409
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
410
|
+
|
|
411
|
+
@see: Function L{pygeodesy.cosineLaw} and methods L{cosineAndoyerLambertTo},
|
|
412
|
+
L{cosineForsytheAndoyerLambertTo}, C{distanceTo*}, L{equirectangularTo},
|
|
413
|
+
L{euclideanTo}, L{flatLocalTo}/L{hubenyTo}, L{flatPolarTo},
|
|
414
|
+
L{haversineTo}, L{thomasTo} and L{vincentysTo}.
|
|
415
|
+
'''
|
|
416
|
+
return self._distanceTo(self._formy.cosineLaw, other, radius, wrap=wrap)
|
|
417
|
+
|
|
418
|
+
@property_RO
|
|
419
|
+
def datum(self): # PYCHOK no cover
|
|
420
|
+
'''I{Must be overloaded}.'''
|
|
421
|
+
notOverloaded(self)
|
|
422
|
+
|
|
423
|
+
def destinationXyz(self, delta, LatLon=None, **LatLon_kwds):
|
|
424
|
+
'''Calculate the destination using a I{local} delta from this point.
|
|
425
|
+
|
|
426
|
+
@arg delta: Local delta to the destination (L{XyzLocal}, L{Enu},
|
|
427
|
+
L{Ned} or L{Local9Tuple}).
|
|
428
|
+
@kwarg LatLon: Optional (geodetic) class to return the destination
|
|
429
|
+
or C{None}.
|
|
430
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
|
|
431
|
+
arguments, ignored if C{B{LatLon} is None}.
|
|
432
|
+
|
|
433
|
+
@return: Destination as a C{B{LatLon}(lat, lon, **B{LatLon_kwds})}
|
|
434
|
+
instance or if C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat,
|
|
435
|
+
lon, height)} respectively L{LatLon4Tuple}C{(lat, lon,
|
|
436
|
+
height, datum)} depending on whether a C{datum} keyword
|
|
437
|
+
is un-/specified.
|
|
438
|
+
|
|
439
|
+
@raise TypeError: Invalid B{C{delta}}, B{C{LatLon}} or B{C{LatLon_kwds}}.
|
|
440
|
+
'''
|
|
441
|
+
t = self._Ltp._local2ecef(delta, nine=True)
|
|
442
|
+
return t.toLatLon(LatLon=LatLon, **_xkwds(LatLon_kwds, name=self.name))
|
|
443
|
+
|
|
444
|
+
def _distanceTo(self, func, other, radius=None, **kwds):
|
|
445
|
+
'''(INTERNAL) Helper for distance methods C{<func>To}.
|
|
446
|
+
'''
|
|
447
|
+
p, r = self.others(other, up=2), radius
|
|
448
|
+
if r is None:
|
|
449
|
+
r = self._datum.ellipsoid.R1 if self._datum else R_M
|
|
450
|
+
return func(self.lat, self.lon, p.lat, p.lon, radius=r, **kwds)
|
|
451
|
+
|
|
452
|
+
def _distanceTo_(self, func_, other, wrap=False, radius=None):
|
|
453
|
+
'''(INTERNAL) Helper for (ellipsoidal) distance methods C{<func>To}.
|
|
454
|
+
'''
|
|
455
|
+
p = self.others(other, up=2)
|
|
456
|
+
D = self.datum
|
|
457
|
+
lam21, phi2, _ = _Wrap.philam3(self.lam, p.phi, p.lam, wrap)
|
|
458
|
+
r = func_(phi2, self.phi, lam21, datum=D)
|
|
459
|
+
return r * (D.ellipsoid.a if radius is None else radius)
|
|
460
|
+
|
|
461
|
+
@property_RO
|
|
462
|
+
def Ecef(self):
|
|
463
|
+
'''Get the ECEF I{class} (L{EcefKarney}), I{once}.
|
|
464
|
+
'''
|
|
465
|
+
LatLonBase.Ecef = E = _MODS.ecef.EcefKarney # overwrite property_RO
|
|
466
|
+
return E
|
|
467
|
+
|
|
468
|
+
@Property_RO
|
|
469
|
+
def _Ecef_forward(self):
|
|
470
|
+
'''(INTERNAL) Helper for L{_ecef9} and L{toEcef} (C{callable}).
|
|
471
|
+
'''
|
|
472
|
+
return self.Ecef(self.datum, name=self.name).forward
|
|
473
|
+
|
|
474
|
+
@Property_RO
|
|
475
|
+
def _ecef9(self):
|
|
476
|
+
'''(INTERNAL) Helper for L{toCartesian}, L{toEcef} and L{toCartesian} (L{Ecef9Tuple}).
|
|
477
|
+
'''
|
|
478
|
+
return self._Ecef_forward(self, M=True)
|
|
479
|
+
|
|
480
|
+
@property_RO
|
|
481
|
+
def ellipsoidalLatLon(self):
|
|
482
|
+
'''Get the C{LatLon type} iff ellipsoidal, overloaded in L{LatLonEllipsoidalBase}.
|
|
483
|
+
'''
|
|
484
|
+
return False
|
|
485
|
+
|
|
486
|
+
@deprecated_method
|
|
487
|
+
def equals(self, other, eps=None): # PYCHOK no cover
|
|
488
|
+
'''DEPRECATED, use method L{isequalTo}.'''
|
|
489
|
+
return self.isequalTo(other, eps=eps)
|
|
490
|
+
|
|
491
|
+
@deprecated_method
|
|
492
|
+
def equals3(self, other, eps=None): # PYCHOK no cover
|
|
493
|
+
'''DEPRECATED, use method L{isequalTo3}.'''
|
|
494
|
+
return self.isequalTo3(other, eps=eps)
|
|
495
|
+
|
|
496
|
+
def equirectangularTo(self, other, **radius_adjust_limit_wrap):
|
|
497
|
+
'''Compute the distance between this and an other point
|
|
498
|
+
using the U{Equirectangular Approximation / Projection
|
|
499
|
+
<https://www.Movable-Type.co.UK/scripts/latlong.html#equirectangular>}.
|
|
500
|
+
|
|
501
|
+
Suitable only for short, non-near-polar distances up to a
|
|
502
|
+
few hundred Km or Miles. Use method L{haversineTo} or
|
|
503
|
+
C{distanceTo*} for more accurate and/or larger distances.
|
|
504
|
+
|
|
505
|
+
@arg other: The other point (C{LatLon}).
|
|
506
|
+
@kwarg radius_adjust_limit_wrap: Optional keyword arguments
|
|
507
|
+
for function L{pygeodesy.equirectangular},
|
|
508
|
+
overriding the default mean C{radius} of this
|
|
509
|
+
point's datum ellipsoid.
|
|
510
|
+
|
|
511
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
512
|
+
|
|
513
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
514
|
+
|
|
515
|
+
@see: Function L{pygeodesy.equirectangular} and methods L{cosineAndoyerLambertTo},
|
|
516
|
+
L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
|
|
517
|
+
C{euclideanTo}, L{flatLocalTo}/L{hubenyTo}, L{flatPolarTo},
|
|
518
|
+
L{haversineTo}, L{thomasTo} and L{vincentysTo}.
|
|
519
|
+
'''
|
|
520
|
+
return self._distanceTo(self._formy.equirectangular, other, **radius_adjust_limit_wrap)
|
|
521
|
+
|
|
522
|
+
def euclideanTo(self, other, **radius_adjust_wrap):
|
|
523
|
+
'''Approximate the C{Euclidian} distance between this and
|
|
524
|
+
an other point.
|
|
525
|
+
|
|
526
|
+
See function L{pygeodesy.euclidean} for the available B{C{options}}.
|
|
527
|
+
|
|
528
|
+
@arg other: The other point (C{LatLon}).
|
|
529
|
+
@kwarg radius_adjust_wrap: Optional keyword arguments for function
|
|
530
|
+
L{pygeodesy.euclidean}, overriding the default mean
|
|
531
|
+
C{radius} of this point's datum ellipsoid.
|
|
532
|
+
|
|
533
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
534
|
+
|
|
535
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
536
|
+
|
|
537
|
+
@see: Function L{pygeodesy.euclidean} and methods L{cosineAndoyerLambertTo},
|
|
538
|
+
L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
|
|
539
|
+
L{equirectangularTo}, L{flatLocalTo}/L{hubenyTo}, L{flatPolarTo},
|
|
540
|
+
L{haversineTo}, L{thomasTo} and L{vincentysTo}.
|
|
541
|
+
'''
|
|
542
|
+
return self._distanceTo(self._formy.euclidean, other, **radius_adjust_wrap)
|
|
543
|
+
|
|
544
|
+
def flatLocalTo(self, other, radius=None, wrap=False):
|
|
545
|
+
'''Compute the distance between this and an other point using the
|
|
546
|
+
U{ellipsoidal Earth to plane projection
|
|
547
|
+
<https://WikiPedia.org/wiki/Geographical_distance#Ellipsoidal_Earth_projected_to_a_plane>}
|
|
548
|
+
aka U{Hubeny<https://www.OVG.AT/de/vgi/files/pdf/3781/>} formula.
|
|
549
|
+
|
|
550
|
+
@arg other: The other point (C{LatLon}).
|
|
551
|
+
@kwarg radius: Mean earth radius (C{meter}) or C{None} for
|
|
552
|
+
the I{equatorial radius} of this point's
|
|
553
|
+
datum ellipsoid.
|
|
554
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
555
|
+
the B{C{other}} point (C{bool}).
|
|
556
|
+
|
|
557
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
558
|
+
|
|
559
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
560
|
+
|
|
561
|
+
@raise ValueError: Invalid B{C{radius}}.
|
|
562
|
+
|
|
563
|
+
@see: Function L{pygeodesy.flatLocal}/L{pygeodesy.hubeny}, methods
|
|
564
|
+
L{cosineAndoyerLambertTo}, L{cosineForsytheAndoyerLambertTo},
|
|
565
|
+
L{cosineLawTo}, C{distanceTo*}, L{equirectangularTo}, L{euclideanTo},
|
|
566
|
+
L{flatPolarTo}, L{haversineTo}, L{thomasTo} and L{vincentysTo} and
|
|
567
|
+
U{local, flat Earth approximation<https://www.edwilliams.org/avform.htm#flat>}.
|
|
568
|
+
'''
|
|
569
|
+
return self._distanceTo_(self._formy.flatLocal_, other, wrap=wrap, radius=
|
|
570
|
+
radius if radius in (None, R_M, _1_0, 1) else Radius(radius)) # PYCHOK kwargs
|
|
571
|
+
|
|
572
|
+
hubenyTo = flatLocalTo # for Karl Hubeny
|
|
573
|
+
|
|
574
|
+
def flatPolarTo(self, other, **radius_wrap):
|
|
575
|
+
'''Compute the distance between this and an other point using
|
|
576
|
+
the U{polar coordinate flat-Earth<https://WikiPedia.org/wiki/
|
|
577
|
+
Geographical_distance#Polar_coordinate_flat-Earth_formula>} formula.
|
|
578
|
+
|
|
579
|
+
@arg other: The other point (C{LatLon}).
|
|
580
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
581
|
+
L{pygeodesy.flatPolar}, overriding the
|
|
582
|
+
default mean C{radius} of this point's
|
|
583
|
+
datum ellipsoid.
|
|
584
|
+
|
|
585
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
586
|
+
|
|
587
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
588
|
+
|
|
589
|
+
@see: Function L{pygeodesy.flatPolar} and methods L{cosineAndoyerLambertTo},
|
|
590
|
+
L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
|
|
591
|
+
L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
|
|
592
|
+
L{haversineTo}, L{thomasTo} and L{vincentysTo}.
|
|
593
|
+
'''
|
|
594
|
+
return self._distanceTo(self._formy.flatPolar, other, **radius_wrap)
|
|
595
|
+
|
|
596
|
+
@property_RO
|
|
597
|
+
def _formy(self):
|
|
598
|
+
'''(INTERNAL) Get module C{formy}, I{once}.
|
|
599
|
+
'''
|
|
600
|
+
LatLonBase._formy = f = _MODS.formy # overwrite property_RO
|
|
601
|
+
return f
|
|
602
|
+
|
|
603
|
+
def hartzell(self, los=False, earth=None):
|
|
604
|
+
'''Compute the intersection of a Line-Of-Sight from this (geodetic) Point-Of-View
|
|
605
|
+
(pov) with this point's ellipsoid surface.
|
|
606
|
+
|
|
607
|
+
@kwarg los: Line-Of-Sight, I{direction} to the ellipsoid (L{Los}, L{Vector3d}),
|
|
608
|
+
C{True} for the I{normal, plumb} onto the surface or I{False} or
|
|
609
|
+
C{None} to point to the center of the ellipsoid.
|
|
610
|
+
@kwarg earth: The earth model (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}
|
|
611
|
+
or C{scalar} radius in C{meter}), overriding this point's C{datum}
|
|
612
|
+
ellipsoid.
|
|
613
|
+
|
|
614
|
+
@return: The intersection (C{LatLon}) with C{.height} set to the distance to
|
|
615
|
+
this C{pov}.
|
|
616
|
+
|
|
617
|
+
@raise IntersectionError: Null or bad C{pov} or B{C{los}}, this C{pov} is inside
|
|
618
|
+
the ellipsoid or B{C{los}} points outside or away from
|
|
619
|
+
the ellipsoid.
|
|
620
|
+
|
|
621
|
+
@raise TypeError: Invalid B{C{los}} or invalid or undefined B{C{earth}} or C{datum}.
|
|
622
|
+
|
|
623
|
+
@see: Function L{hartzell<pygeodesy.formy.hartzell>} for further details.
|
|
624
|
+
'''
|
|
625
|
+
return self._formy._hartzell(self, los, earth, LatLon=self.classof)
|
|
626
|
+
|
|
627
|
+
def haversineTo(self, other, **radius_wrap):
|
|
628
|
+
'''Compute the distance between this and an other point using the
|
|
629
|
+
U{Haversine<https://www.Movable-Type.co.UK/scripts/latlong.html>}
|
|
630
|
+
formula.
|
|
631
|
+
|
|
632
|
+
@arg other: The other point (C{LatLon}).
|
|
633
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
634
|
+
L{pygeodesy.haversine}, overriding the
|
|
635
|
+
default mean C{radius} of this point's
|
|
636
|
+
datum ellipsoid.
|
|
637
|
+
|
|
638
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
639
|
+
|
|
640
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
641
|
+
|
|
642
|
+
@see: Function L{pygeodesy.haversine} and methods L{cosineAndoyerLambertTo},
|
|
643
|
+
L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
|
|
644
|
+
L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
|
|
645
|
+
L{flatPolarTo}, L{thomasTo} and L{vincentysTo}.
|
|
646
|
+
'''
|
|
647
|
+
return self._distanceTo(self._formy.haversine, other, **radius_wrap)
|
|
648
|
+
|
|
649
|
+
def _havg(self, other, f=_0_5, h=None):
|
|
650
|
+
'''(INTERNAL) Weighted, average height.
|
|
651
|
+
|
|
652
|
+
@arg other: An other point (C{LatLon}).
|
|
653
|
+
@kwarg f: Optional fraction (C{float}).
|
|
654
|
+
@kwarg h: Overriding height (C{meter}).
|
|
655
|
+
|
|
656
|
+
@return: Average, fractional height (C{float}) or
|
|
657
|
+
the overriding height B{C{h}} (C{Height}).
|
|
658
|
+
'''
|
|
659
|
+
return Height(h) if h is not None else \
|
|
660
|
+
_MODS.fmath.favg(self.height, other.height, f=f)
|
|
661
|
+
|
|
662
|
+
@Property
|
|
663
|
+
def height(self):
|
|
664
|
+
'''Get the height (C{meter}).
|
|
665
|
+
'''
|
|
666
|
+
return self._height
|
|
667
|
+
|
|
668
|
+
@height.setter # PYCHOK setter!
|
|
669
|
+
def height(self, height):
|
|
670
|
+
'''Set the height (C{meter}).
|
|
671
|
+
|
|
672
|
+
@raise TypeError: Invalid B{C{height}} C{type}.
|
|
673
|
+
|
|
674
|
+
@raise ValueError: Invalid B{C{height}}.
|
|
675
|
+
'''
|
|
676
|
+
h = Height(height)
|
|
677
|
+
if self._height != h:
|
|
678
|
+
_update_all(self)
|
|
679
|
+
self._height = h
|
|
680
|
+
|
|
681
|
+
def _heigHt(self, height):
|
|
682
|
+
'''(INTERNAL) Overriding this C{height}.
|
|
683
|
+
'''
|
|
684
|
+
return self.height if height is None else Height(height)
|
|
685
|
+
|
|
686
|
+
def height4(self, earth=None, normal=True, LatLon=None, **LatLon_kwds):
|
|
687
|
+
'''Compute the projection of this point on and the height above or below
|
|
688
|
+
this datum's ellipsoid surface.
|
|
689
|
+
|
|
690
|
+
@kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,
|
|
691
|
+
I{overriding} this datum (L{Datum}, L{Ellipsoid},
|
|
692
|
+
L{Ellipsoid2}, L{a_f2Tuple}, L{Triaxial}, L{Triaxial_},
|
|
693
|
+
L{JacobiConformal} or C{meter}, conventionally).
|
|
694
|
+
@kwarg normal: If C{True} the projection is the normal to this
|
|
695
|
+
ellipsoid's surface, otherwise the intersection of the
|
|
696
|
+
I{radial} line to this ellipsoid's center (C{bool}).
|
|
697
|
+
@kwarg LatLon: Optional class to return the projection, height and
|
|
698
|
+
datum (C{LatLon}) or C{None}.
|
|
699
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments,
|
|
700
|
+
ignored if C{B{LatLon} is None}.
|
|
701
|
+
|
|
702
|
+
@note: Use keyword argument C{height=0} to override C{B{LatLon}.height}
|
|
703
|
+
to {0} or any other C{scalar}, conventionally in C{meter}.
|
|
704
|
+
|
|
705
|
+
@return: An instance of class B{C{LatLon}} or if C{B{LatLon} is None}, a
|
|
706
|
+
L{Vector4Tuple}C{(x, y, z, h)} with the I{projection} C{x}, C{y}
|
|
707
|
+
and C{z} coordinates and height C{h} in C{meter}, conventionally.
|
|
708
|
+
|
|
709
|
+
@raise TriaxialError: No convergence in triaxial root finding.
|
|
710
|
+
|
|
711
|
+
@raise TypeError: Invalid B{C{earth}} or triaxial B{C{earth}} couldn't be
|
|
712
|
+
converted to biaxial B{C{LatLon}} datum.
|
|
713
|
+
|
|
714
|
+
@see: Methods L{Ellipsoid.height4} and L{Triaxial_.height4} for more information.
|
|
715
|
+
'''
|
|
716
|
+
c = self.toCartesian()
|
|
717
|
+
if LatLon is None:
|
|
718
|
+
r = c.height4(earth=earth, normal=normal)
|
|
719
|
+
else:
|
|
720
|
+
c = c.height4(earth=earth, normal=normal, Cartesian=c.classof, height=0)
|
|
721
|
+
r = c.toLatLon(LatLon=LatLon, **_xkwds(LatLon_kwds, datum=c.datum, height=c.height))
|
|
722
|
+
if r.datum != c.datum:
|
|
723
|
+
raise _TypeError(earth=earth, datum=r.datum)
|
|
724
|
+
return r
|
|
725
|
+
|
|
726
|
+
def heightStr(self, prec=-2, m=_m_):
|
|
727
|
+
'''Return this point's B{C{height}} as C{str}ing.
|
|
728
|
+
|
|
729
|
+
@kwarg prec: Number of (decimal) digits, unstripped (C{int}).
|
|
730
|
+
@kwarg m: Optional unit of the height (C{str}).
|
|
731
|
+
|
|
732
|
+
@see: Function L{pygeodesy.hstr}.
|
|
733
|
+
'''
|
|
734
|
+
return _MODS.streprs.hstr(self.height, prec=prec, m=m)
|
|
735
|
+
|
|
736
|
+
def intersecant2(self, *args, **kwds): # PYCHOK no cover
|
|
737
|
+
'''B{Not implemented}, throws a C{NotImplementedError} always.'''
|
|
738
|
+
notImplemented(self, *args, **kwds)
|
|
739
|
+
|
|
740
|
+
def _intersecend2(self, p, q, wrap, height, g_or_r, P, Q, unused): # in .LatLonEllipsoidalBaseDI.intersecant2
|
|
741
|
+
'''(INTERNAL) Interpolate 2 heights along a geodesic or rhumb
|
|
742
|
+
line and return the 2 intercant points accordingly.
|
|
743
|
+
'''
|
|
744
|
+
if height is None:
|
|
745
|
+
hp = hq = _xattr(p, height=INT0)
|
|
746
|
+
h = _xattr(q, height=hp) # if isLatLon(q) else hp
|
|
747
|
+
if h != hp:
|
|
748
|
+
s = g_or_r._Inverse(p, q, wrap).s12
|
|
749
|
+
if s: # fmath.fidw?
|
|
750
|
+
s = (h - hp) / s # slope
|
|
751
|
+
hq += s * Q.s12
|
|
752
|
+
hp += s * P.s12
|
|
753
|
+
else:
|
|
754
|
+
hp = hq = _MODS.fmath.favg(hp, h)
|
|
755
|
+
else:
|
|
756
|
+
hp = hq = Height(height)
|
|
757
|
+
|
|
758
|
+
# n = self.name or unused.__name__
|
|
759
|
+
p = q = self.classof(P.lat2, P.lon2, datum=g_or_r.datum, height=hp) # name=n
|
|
760
|
+
p._iteration = P.iteration
|
|
761
|
+
if P is not Q:
|
|
762
|
+
q = self.classof(Q.lat2, Q.lon2, datum=g_or_r.datum, height=hq) # name=n
|
|
763
|
+
q._iteration = Q.iteration
|
|
764
|
+
return p, q
|
|
765
|
+
|
|
766
|
+
@deprecated_method
|
|
767
|
+
def isantipode(self, other, eps=EPS): # PYCHOK no cover
|
|
768
|
+
'''DEPRECATED, use method L{isantipodeTo}.'''
|
|
769
|
+
return self.isantipodeTo(other, eps=eps)
|
|
770
|
+
|
|
771
|
+
def isantipodeTo(self, other, eps=EPS):
|
|
772
|
+
'''Check whether this and an other point are antipodal,
|
|
773
|
+
on diametrically opposite sides of the earth.
|
|
774
|
+
|
|
775
|
+
@arg other: The other point (C{LatLon}).
|
|
776
|
+
@kwarg eps: Tolerance for near-equality (C{degrees}).
|
|
777
|
+
|
|
778
|
+
@return: C{True} if points are antipodal within the given
|
|
779
|
+
tolerance, C{False} otherwise.
|
|
780
|
+
'''
|
|
781
|
+
p = self.others(other)
|
|
782
|
+
return self._formy.isantipode(*(self.latlon + p.latlon), eps=eps)
|
|
783
|
+
|
|
784
|
+
@Property_RO
|
|
785
|
+
def isEllipsoidal(self):
|
|
786
|
+
'''Check whether this point is ellipsoidal (C{bool} or C{None} if unknown).
|
|
787
|
+
'''
|
|
788
|
+
return self.datum.isEllipsoidal if self._datum else None
|
|
789
|
+
|
|
790
|
+
def isequalTo(self, other, eps=None):
|
|
791
|
+
'''Compare this point with an other point, I{ignoring} height.
|
|
792
|
+
|
|
793
|
+
@arg other: The other point (C{LatLon}).
|
|
794
|
+
@kwarg eps: Tolerance for equality (C{degrees}).
|
|
795
|
+
|
|
796
|
+
@return: C{True} if both points are identical,
|
|
797
|
+
I{ignoring} height, C{False} otherwise.
|
|
798
|
+
|
|
799
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}
|
|
800
|
+
or mismatch of the B{C{other}} and
|
|
801
|
+
this C{class} or C{type}.
|
|
802
|
+
|
|
803
|
+
@raise UnitError: Invalid B{C{eps}}.
|
|
804
|
+
|
|
805
|
+
@see: Method L{isequalTo3}.
|
|
806
|
+
'''
|
|
807
|
+
return self._formy._isequalTo(self, self.others(other), eps=eps)
|
|
808
|
+
|
|
809
|
+
def isequalTo3(self, other, eps=None):
|
|
810
|
+
'''Compare this point with an other point, I{including} height.
|
|
811
|
+
|
|
812
|
+
@arg other: The other point (C{LatLon}).
|
|
813
|
+
@kwarg eps: Tolerance for equality (C{degrees}).
|
|
814
|
+
|
|
815
|
+
@return: C{True} if both points are identical I{including}
|
|
816
|
+
height, C{False} otherwise.
|
|
817
|
+
|
|
818
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}
|
|
819
|
+
or mismatch of the B{C{other}} and this
|
|
820
|
+
C{class} or C{type}.
|
|
821
|
+
|
|
822
|
+
@see: Method L{isequalTo}.
|
|
823
|
+
'''
|
|
824
|
+
return self.height == self.others(other).height and \
|
|
825
|
+
self._formy._isequalTo(self, other, eps=eps)
|
|
826
|
+
|
|
827
|
+
@Property_RO
|
|
828
|
+
def isnormal(self):
|
|
829
|
+
'''Return C{True} if this point is normal (C{bool}),
|
|
830
|
+
meaning C{abs(lat) <= 90} and C{abs(lon) <= 180}.
|
|
831
|
+
|
|
832
|
+
@see: Methods L{normal}, L{toNormal} and functions L{isnormal
|
|
833
|
+
<pygeodesy.isnormal>} and L{normal<pygeodesy.normal>}.
|
|
834
|
+
'''
|
|
835
|
+
return self._formy.isnormal(self.lat, self.lon, eps=0)
|
|
836
|
+
|
|
837
|
+
@Property_RO
|
|
838
|
+
def isSpherical(self):
|
|
839
|
+
'''Check whether this point is spherical (C{bool} or C{None} if unknown).
|
|
840
|
+
'''
|
|
841
|
+
return self.datum.isSpherical if self._datum else None
|
|
842
|
+
|
|
843
|
+
@Property_RO
|
|
844
|
+
def lam(self):
|
|
845
|
+
'''Get the longitude (B{C{radians}}).
|
|
846
|
+
'''
|
|
847
|
+
return radians(self.lon)
|
|
848
|
+
|
|
849
|
+
@Property
|
|
850
|
+
def lat(self):
|
|
851
|
+
'''Get the latitude (C{degrees90}).
|
|
852
|
+
'''
|
|
853
|
+
return self._lat
|
|
854
|
+
|
|
855
|
+
@lat.setter # PYCHOK setter!
|
|
856
|
+
def lat(self, lat):
|
|
857
|
+
'''Set the latitude (C{str[N|S]} or C{degrees}).
|
|
858
|
+
|
|
859
|
+
@raise ValueError: Invalid B{C{lat}}.
|
|
860
|
+
'''
|
|
861
|
+
lat = Lat(lat) # parseDMS(lat, suffix=_NS_, clip=90)
|
|
862
|
+
if self._lat != lat:
|
|
863
|
+
_update_all(self)
|
|
864
|
+
self._lat = lat
|
|
865
|
+
|
|
866
|
+
@Property
|
|
867
|
+
def latlon(self):
|
|
868
|
+
'''Get the lat- and longitude (L{LatLon2Tuple}C{(lat, lon)}).
|
|
869
|
+
'''
|
|
870
|
+
return LatLon2Tuple(self._lat, self._lon, name=self.name)
|
|
871
|
+
|
|
872
|
+
@latlon.setter # PYCHOK setter!
|
|
873
|
+
def latlon(self, latlonh):
|
|
874
|
+
'''Set the lat- and longitude and optionally the height
|
|
875
|
+
(2- or 3-tuple or comma- or space-separated C{str}
|
|
876
|
+
of C{degrees90}, C{degrees180} and C{meter}).
|
|
877
|
+
|
|
878
|
+
@raise TypeError: Height of B{C{latlonh}} not C{scalar} or
|
|
879
|
+
B{C{latlonh}} not C{list} or C{tuple}.
|
|
880
|
+
|
|
881
|
+
@raise ValueError: Invalid B{C{latlonh}} or M{len(latlonh)}.
|
|
882
|
+
|
|
883
|
+
@see: Function L{pygeodesy.parse3llh} to parse a B{C{latlonh}}
|
|
884
|
+
string into a 3-tuple C{(lat, lon, h)}.
|
|
885
|
+
'''
|
|
886
|
+
if isstr(latlonh):
|
|
887
|
+
latlonh = parse3llh(latlonh, height=self.height)
|
|
888
|
+
else:
|
|
889
|
+
_xinstanceof(list, tuple, latlonh=latlonh)
|
|
890
|
+
if len(latlonh) == 3:
|
|
891
|
+
h = Height(latlonh[2], name=Fmt.SQUARE(latlonh=2))
|
|
892
|
+
elif len(latlonh) != 2:
|
|
893
|
+
raise _ValueError(latlonh=latlonh)
|
|
894
|
+
else:
|
|
895
|
+
h = self.height
|
|
896
|
+
|
|
897
|
+
llh = Lat(latlonh[0]), Lon(latlonh[1]), h # parseDMS2(latlonh[0], latlonh[1])
|
|
898
|
+
if (self._lat, self._lon, self._height) != llh:
|
|
899
|
+
_update_all(self)
|
|
900
|
+
self._lat, self._lon, self._height = llh
|
|
901
|
+
|
|
902
|
+
def latlon2(self, ndigits=0):
|
|
903
|
+
'''Return this point's lat- and longitude in C{degrees}, rounded.
|
|
904
|
+
|
|
905
|
+
@kwarg ndigits: Number of (decimal) digits (C{int}).
|
|
906
|
+
|
|
907
|
+
@return: A L{LatLon2Tuple}C{(lat, lon)}, both C{float}
|
|
908
|
+
and rounded away from zero.
|
|
909
|
+
|
|
910
|
+
@note: The C{round}ed values are always C{float}, also
|
|
911
|
+
if B{C{ndigits}} is omitted.
|
|
912
|
+
'''
|
|
913
|
+
return LatLon2Tuple(round(self.lat, ndigits),
|
|
914
|
+
round(self.lon, ndigits), name=self.name)
|
|
915
|
+
|
|
916
|
+
@deprecated_method
|
|
917
|
+
def latlon_(self, ndigits=0): # PYCHOK no cover
|
|
918
|
+
'''DEPRECATED, use method L{latlon2}.'''
|
|
919
|
+
return self.latlon2(ndigits=ndigits)
|
|
920
|
+
|
|
921
|
+
latlon2round = latlon_ # PYCHOK no cover
|
|
922
|
+
|
|
923
|
+
@Property
|
|
924
|
+
def latlonheight(self):
|
|
925
|
+
'''Get the lat-, longitude and height (L{LatLon3Tuple}C{(lat, lon, height)}).
|
|
926
|
+
'''
|
|
927
|
+
return self.latlon.to3Tuple(self.height)
|
|
928
|
+
|
|
929
|
+
@latlonheight.setter # PYCHOK setter!
|
|
930
|
+
def latlonheight(self, latlonh):
|
|
931
|
+
'''Set the lat- and longitude and optionally the height
|
|
932
|
+
(2- or 3-tuple or comma- or space-separated C{str} of
|
|
933
|
+
C{degrees90}, C{degrees180} and C{meter}).
|
|
934
|
+
|
|
935
|
+
@see: Property L{latlon} for more details.
|
|
936
|
+
'''
|
|
937
|
+
self.latlon = latlonh
|
|
938
|
+
|
|
939
|
+
@Property
|
|
940
|
+
def lon(self):
|
|
941
|
+
'''Get the longitude (C{degrees180}).
|
|
942
|
+
'''
|
|
943
|
+
return self._lon
|
|
944
|
+
|
|
945
|
+
@lon.setter # PYCHOK setter!
|
|
946
|
+
def lon(self, lon):
|
|
947
|
+
'''Set the longitude (C{str[E|W]} or C{degrees}).
|
|
948
|
+
|
|
949
|
+
@raise ValueError: Invalid B{C{lon}}.
|
|
950
|
+
'''
|
|
951
|
+
lon = Lon(lon) # parseDMS(lon, suffix=_EW_, clip=180)
|
|
952
|
+
if self._lon != lon:
|
|
953
|
+
_update_all(self)
|
|
954
|
+
self._lon = lon
|
|
955
|
+
|
|
956
|
+
@property_RO
|
|
957
|
+
def _ltp(self):
|
|
958
|
+
'''(INTERNAL) Get the C{.ltp} module, I{once}.
|
|
959
|
+
'''
|
|
960
|
+
LatLonBase._ltp = m = _MODS.ltp # overwrite property_RO
|
|
961
|
+
return m
|
|
962
|
+
|
|
963
|
+
@Property_RO
|
|
964
|
+
def _Ltp(self):
|
|
965
|
+
'''(INTERNAL) Cache for L{toLtp}.
|
|
966
|
+
'''
|
|
967
|
+
return self._ltp.Ltp(self, ecef=self.Ecef(self.datum), name=self.name)
|
|
968
|
+
|
|
969
|
+
def nearestOn6(self, points, closed=False, height=None, wrap=False):
|
|
970
|
+
'''Locate the point on a path or polygon closest to this point.
|
|
971
|
+
|
|
972
|
+
Points are converted to and distances are computed in
|
|
973
|
+
I{geocentric}, cartesian space.
|
|
974
|
+
|
|
975
|
+
@arg points: The path or polygon points (C{LatLon}[]).
|
|
976
|
+
@kwarg closed: Optionally, close the polygon (C{bool}).
|
|
977
|
+
@kwarg height: Optional height, overriding the height of
|
|
978
|
+
this and all other points (C{meter}). If
|
|
979
|
+
C{None}, take the height of points into
|
|
980
|
+
account for distances.
|
|
981
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
982
|
+
the B{C{points}} (C{bool}).
|
|
983
|
+
|
|
984
|
+
@return: A L{NearestOn6Tuple}C{(closest, distance, fi, j,
|
|
985
|
+
start, end)} with the C{closest}, the C{start}
|
|
986
|
+
and the C{end} point each an instance of this
|
|
987
|
+
C{LatLon} and C{distance} in C{meter}, same
|
|
988
|
+
units as the cartesian axes.
|
|
989
|
+
|
|
990
|
+
@raise PointsError: Insufficient number of B{C{points}}.
|
|
991
|
+
|
|
992
|
+
@raise TypeError: Some B{C{points}} or some B{C{points}}'
|
|
993
|
+
C{Ecef} invalid.
|
|
994
|
+
|
|
995
|
+
@raise ValueError: Some B{C{points}}' C{Ecef} is incompatible.
|
|
996
|
+
|
|
997
|
+
@see: Function L{nearestOn6<pygeodesy.nearestOn6>}.
|
|
998
|
+
'''
|
|
999
|
+
def _cs(Ps, h, w, C):
|
|
1000
|
+
p = None # not used
|
|
1001
|
+
for i, q in Ps.enumerate():
|
|
1002
|
+
if w and i:
|
|
1003
|
+
q = _unrollon(p, q)
|
|
1004
|
+
yield C(height=h, i=i, up=3, points=q)
|
|
1005
|
+
p = q
|
|
1006
|
+
|
|
1007
|
+
C = self._toCartesianEcef # to verify datum and Ecef
|
|
1008
|
+
Ps = self.PointsIter(points, wrap=wrap)
|
|
1009
|
+
|
|
1010
|
+
c = C(height=height, this=self) # this Cartesian
|
|
1011
|
+
t = nearestOn6(c, _cs(Ps, height, wrap, C), closed=closed)
|
|
1012
|
+
c, s, e = t.closest, t.start, t.end
|
|
1013
|
+
|
|
1014
|
+
kwds = _xkwds_not(None, LatLon=self.classof, # this LatLon
|
|
1015
|
+
height=height)
|
|
1016
|
+
_r = self.Ecef(self.datum).reverse
|
|
1017
|
+
p = _r(c).toLatLon(**kwds)
|
|
1018
|
+
s = _r(s).toLatLon(**kwds) if s is not c else p
|
|
1019
|
+
e = _r(e).toLatLon(**kwds) if e is not c else p
|
|
1020
|
+
return t.dup(closest=p, start=s, end=e)
|
|
1021
|
+
|
|
1022
|
+
def nearestTo(self, *args, **kwds): # PYCHOK no cover
|
|
1023
|
+
'''B{Not implemented}, throws a C{NotImplementedError} always.'''
|
|
1024
|
+
notImplemented(self, *args, **kwds)
|
|
1025
|
+
|
|
1026
|
+
def normal(self):
|
|
1027
|
+
'''Normalize this point I{in-place} to C{abs(lat) <= 90} and
|
|
1028
|
+
C{abs(lon) <= 180}.
|
|
1029
|
+
|
|
1030
|
+
@return: C{True} if this point was I{normal}, C{False} if it
|
|
1031
|
+
wasn't (but is now).
|
|
1032
|
+
|
|
1033
|
+
@see: Property L{isnormal} and method L{toNormal}.
|
|
1034
|
+
'''
|
|
1035
|
+
n = self.isnormal
|
|
1036
|
+
if not n:
|
|
1037
|
+
self.latlon = self._formy.normal(*self.latlon)
|
|
1038
|
+
return n
|
|
1039
|
+
|
|
1040
|
+
@property_RO
|
|
1041
|
+
def _N_vector(self):
|
|
1042
|
+
'''(INTERNAL) Get the C{Nvector} (C{nvectorBase._N_vector_})
|
|
1043
|
+
'''
|
|
1044
|
+
x, y, z = self._n_xyz3
|
|
1045
|
+
return _MODS.nvectorBase._N_vector_(x, y, z, h=self.height, name=self.name)
|
|
1046
|
+
|
|
1047
|
+
@Property_RO
|
|
1048
|
+
def _n_xyz3(self):
|
|
1049
|
+
'''(INTERNAL) Get the n-vector components as L{Vector3Tuple}.
|
|
1050
|
+
'''
|
|
1051
|
+
return self._formy.philam2n_xyz(self.phi, self.lam, name=self.name)
|
|
1052
|
+
|
|
1053
|
+
@Property_RO
|
|
1054
|
+
def phi(self):
|
|
1055
|
+
'''Get the latitude (B{C{radians}}).
|
|
1056
|
+
'''
|
|
1057
|
+
return radians(self.lat)
|
|
1058
|
+
|
|
1059
|
+
@Property_RO
|
|
1060
|
+
def philam(self):
|
|
1061
|
+
'''Get the lat- and longitude (L{PhiLam2Tuple}C{(phi, lam)}).
|
|
1062
|
+
'''
|
|
1063
|
+
return PhiLam2Tuple(self.phi, self.lam, name=self.name)
|
|
1064
|
+
|
|
1065
|
+
def philam2(self, ndigits=0):
|
|
1066
|
+
'''Return this point's lat- and longitude in C{radians}, rounded.
|
|
1067
|
+
|
|
1068
|
+
@kwarg ndigits: Number of (decimal) digits (C{int}).
|
|
1069
|
+
|
|
1070
|
+
@return: A L{PhiLam2Tuple}C{(phi, lam)}, both C{float}
|
|
1071
|
+
and rounded away from zero.
|
|
1072
|
+
|
|
1073
|
+
@note: The C{round}ed values are always C{float}, also
|
|
1074
|
+
if B{C{ndigits}} is omitted.
|
|
1075
|
+
'''
|
|
1076
|
+
return PhiLam2Tuple(round(self.phi, ndigits),
|
|
1077
|
+
round(self.lam, ndigits), name=self.name)
|
|
1078
|
+
|
|
1079
|
+
@Property_RO
|
|
1080
|
+
def philamheight(self):
|
|
1081
|
+
'''Get the lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
|
|
1082
|
+
'''
|
|
1083
|
+
return self.philam.to3Tuple(self.height)
|
|
1084
|
+
|
|
1085
|
+
@deprecated_method
|
|
1086
|
+
def points(self, points, closed=True): # PYCHOK no cover
|
|
1087
|
+
'''DEPRECATED, use method L{points2}.'''
|
|
1088
|
+
return self.points2(points, closed=closed)
|
|
1089
|
+
|
|
1090
|
+
def points2(self, points, closed=True):
|
|
1091
|
+
'''Check a path or polygon represented by points.
|
|
1092
|
+
|
|
1093
|
+
@arg points: The path or polygon points (C{LatLon}[])
|
|
1094
|
+
@kwarg closed: Optionally, consider the polygon closed,
|
|
1095
|
+
ignoring any duplicate or closing final
|
|
1096
|
+
B{C{points}} (C{bool}).
|
|
1097
|
+
|
|
1098
|
+
@return: A L{Points2Tuple}C{(number, points)}, an C{int}
|
|
1099
|
+
and C{list} or C{tuple}.
|
|
1100
|
+
|
|
1101
|
+
@raise PointsError: Insufficient number of B{C{points}}.
|
|
1102
|
+
|
|
1103
|
+
@raise TypeError: Some B{C{points}} are not C{LatLon}.
|
|
1104
|
+
'''
|
|
1105
|
+
return _MODS.iters.points2(points, closed=closed, base=self)
|
|
1106
|
+
|
|
1107
|
+
def PointsIter(self, points, loop=0, dedup=False, wrap=False):
|
|
1108
|
+
'''Return a C{PointsIter} iterator.
|
|
1109
|
+
|
|
1110
|
+
@arg points: The path or polygon points (C{LatLon}[])
|
|
1111
|
+
@kwarg loop: Number of loop-back points (non-negative C{int}).
|
|
1112
|
+
@kwarg dedup: Skip duplicate points (C{bool}).
|
|
1113
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} the
|
|
1114
|
+
enum-/iterated B{C{points}} (C{bool}).
|
|
1115
|
+
|
|
1116
|
+
@return: A new C{PointsIter} iterator.
|
|
1117
|
+
|
|
1118
|
+
@raise PointsError: Insufficient number of B{C{points}}.
|
|
1119
|
+
'''
|
|
1120
|
+
return PointsIter(points, base=self, loop=loop, dedup=dedup, wrap=wrap)
|
|
1121
|
+
|
|
1122
|
+
def radii11(self, point2, point3, wrap=False):
|
|
1123
|
+
'''Return the radii of the C{Circum-}, C{In-}, I{Soddy} and C{Tangent}
|
|
1124
|
+
circles of a (planar) triangle formed by this and two other points.
|
|
1125
|
+
|
|
1126
|
+
@arg point2: Second point (C{LatLon}).
|
|
1127
|
+
@arg point3: Third point (C{LatLon}).
|
|
1128
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} B{C{point2}} and
|
|
1129
|
+
B{C{point3}} (C{bool}).
|
|
1130
|
+
|
|
1131
|
+
@return: L{Radii11Tuple}C{(rA, rB, rC, cR, rIn, riS, roS, a, b, c, s)}.
|
|
1132
|
+
|
|
1133
|
+
@raise IntersectionError: Near-coincident or -colinear points.
|
|
1134
|
+
|
|
1135
|
+
@raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
|
|
1136
|
+
|
|
1137
|
+
@see: Function L{pygeodesy.radii11}, U{Incircle
|
|
1138
|
+
<https://MathWorld.Wolfram.com/Incircle.html>}, U{Soddy Circles
|
|
1139
|
+
<https://MathWorld.Wolfram.com/SoddyCircles.html>} and U{Tangent
|
|
1140
|
+
Circles<https://MathWorld.Wolfram.com/TangentCircles.html>}.
|
|
1141
|
+
'''
|
|
1142
|
+
with _toCartesian3(self, point2, point3, wrap) as cs:
|
|
1143
|
+
return _radii11ABC(*cs, useZ=True)[0]
|
|
1144
|
+
|
|
1145
|
+
def _rhumb3(self, exact, radius): # != .sphericalBase._rhumbs3
|
|
1146
|
+
'''(INTERNAL) Get the C{rhumb} for this point's datum or for
|
|
1147
|
+
the B{C{radius}}' earth model iff non-C{None}.
|
|
1148
|
+
'''
|
|
1149
|
+
try:
|
|
1150
|
+
d = self._rhumb3dict
|
|
1151
|
+
t = d[(exact, radius)]
|
|
1152
|
+
except KeyError:
|
|
1153
|
+
D = self.datum if radius is None else \
|
|
1154
|
+
_spherical_datum(radius) # ellipsoidal OK
|
|
1155
|
+
try:
|
|
1156
|
+
r = D.ellipsoid.rhumb_(exact=exact) # or D.isSpherical
|
|
1157
|
+
except AttributeError as x:
|
|
1158
|
+
raise _AttributeError(datum=D, radius=radius, cause=x)
|
|
1159
|
+
t = r, D, _MODS.karney.Caps
|
|
1160
|
+
while d:
|
|
1161
|
+
d.popitem()
|
|
1162
|
+
d[(exact, radius)] = t # cache 3-tuple
|
|
1163
|
+
return t
|
|
1164
|
+
|
|
1165
|
+
@Property_RO
|
|
1166
|
+
def _rhumb3dict(self): # in rhumbIntersecant2 below
|
|
1167
|
+
return {} # single-item cache
|
|
1168
|
+
|
|
1169
|
+
def rhumbAzimuthTo(self, other, exact=False, radius=None, wrap=False, b360=False):
|
|
1170
|
+
'''Return the azimuth (bearing) of a rhumb line (loxodrome) between this
|
|
1171
|
+
and an other (ellipsoidal) point.
|
|
1172
|
+
|
|
1173
|
+
@arg other: The other point (C{LatLon}).
|
|
1174
|
+
@kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
|
|
1175
|
+
method L{Ellipsoid.rhumb_}.
|
|
1176
|
+
@kwarg radius: Optional earth radius (C{meter}) or earth model (L{Datum},
|
|
1177
|
+
L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}), overriding
|
|
1178
|
+
this point's datum.
|
|
1179
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{other}}
|
|
1180
|
+
point (C{bool}).
|
|
1181
|
+
@kwarg b360: If C{True}, return the azimuth in the bearing range.
|
|
1182
|
+
|
|
1183
|
+
@return: Rhumb azimuth (compass C{degrees180} or C{degrees360}).
|
|
1184
|
+
|
|
1185
|
+
@raise TypeError: The B{C{other}} point is incompatible or B{C{radius}}
|
|
1186
|
+
is invalid.
|
|
1187
|
+
'''
|
|
1188
|
+
r, _, Cs = self._rhumb3(exact, radius)
|
|
1189
|
+
z = r._Inverse(self, other, wrap, outmask=Cs.AZIMUTH).azi12
|
|
1190
|
+
return _umod_360(z + _360_0) if b360 else z
|
|
1191
|
+
|
|
1192
|
+
def rhumbDestination(self, distance, azimuth, exact=False, radius=None, height=None):
|
|
1193
|
+
'''Return the destination point having travelled the given distance from
|
|
1194
|
+
this point along a rhumb line (loxodrome) of the given azimuth.
|
|
1195
|
+
|
|
1196
|
+
@arg distance: Distance travelled (C{meter}, same units as this point's
|
|
1197
|
+
datum (ellipsoid) axes or B{C{radius}}, may be negative.
|
|
1198
|
+
@arg azimuth: Azimuth (bearing) of the rhumb line (compass C{degrees}).
|
|
1199
|
+
@kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
|
|
1200
|
+
method L{Ellipsoid.rhumb_}.
|
|
1201
|
+
@kwarg radius: Optional earth radius (C{meter}) or earth model (L{Datum},
|
|
1202
|
+
L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}), overriding
|
|
1203
|
+
this point's datum.
|
|
1204
|
+
@kwarg height: Optional height, overriding the default height (C{meter}).
|
|
1205
|
+
|
|
1206
|
+
@return: The destination point (ellipsoidal C{LatLon}).
|
|
1207
|
+
|
|
1208
|
+
@raise TypeError: Invalid B{C{radius}}.
|
|
1209
|
+
|
|
1210
|
+
@raise ValueError: Invalid B{C{distance}}, B{C{azimuth}}, B{C{radius}}
|
|
1211
|
+
or B{C{height}}.
|
|
1212
|
+
'''
|
|
1213
|
+
r, D, _ = self._rhumb3(exact, radius)
|
|
1214
|
+
d = r._Direct(self, azimuth, distance)
|
|
1215
|
+
h = self._heigHt(height)
|
|
1216
|
+
return self.classof(d.lat2, d.lon2, datum=D, height=h)
|
|
1217
|
+
|
|
1218
|
+
def rhumbDistanceTo(self, other, exact=False, radius=None, wrap=False):
|
|
1219
|
+
'''Return the distance from this to an other point along a rhumb line
|
|
1220
|
+
(loxodrome).
|
|
1221
|
+
|
|
1222
|
+
@arg other: The other point (C{LatLon}).
|
|
1223
|
+
@kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
|
|
1224
|
+
method L{Ellipsoid.rhumb_}.
|
|
1225
|
+
@kwarg radius: Optional earth radius (C{meter}) or earth model (L{Datum},
|
|
1226
|
+
L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}), overriding
|
|
1227
|
+
this point's datum.
|
|
1228
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{other}}
|
|
1229
|
+
point (C{bool}).
|
|
1230
|
+
|
|
1231
|
+
@return: Distance (C{meter}, the same units as this point's datum
|
|
1232
|
+
(ellipsoid) axes or B{C{radius}}.
|
|
1233
|
+
|
|
1234
|
+
@raise TypeError: The B{C{other}} point is incompatible or B{C{radius}}
|
|
1235
|
+
is invalid.
|
|
1236
|
+
|
|
1237
|
+
@raise ValueError: Invalid B{C{radius}}.
|
|
1238
|
+
'''
|
|
1239
|
+
r, _, Cs = self._rhumb3(exact, radius)
|
|
1240
|
+
return r._Inverse(self, other, wrap, outmask=Cs.DISTANCE).s12
|
|
1241
|
+
|
|
1242
|
+
def rhumbIntersecant2(self, circle, point, other, height=None,
|
|
1243
|
+
**exact_radius_wrap_eps_tol):
|
|
1244
|
+
'''Compute the intersections of a circle and a rhumb line given as two
|
|
1245
|
+
points or as a point and azimuth.
|
|
1246
|
+
|
|
1247
|
+
@arg circle: Radius of the circle centered at this location (C{meter}),
|
|
1248
|
+
or a point on the circle (this C{LatLon}).
|
|
1249
|
+
@arg point: The start point of the rhumb line (this C{LatLon}).
|
|
1250
|
+
@arg other: An other point I{on} (this C{LatLon}) or the azimuth I{of}
|
|
1251
|
+
(compass C{degrees}) the rhumb line.
|
|
1252
|
+
@kwarg height: Optional height for the intersection points (C{meter},
|
|
1253
|
+
conventionally) or C{None} for interpolated heights.
|
|
1254
|
+
@kwarg exact_radius_wrap_eps_tol: Optional keyword arguments, see
|
|
1255
|
+
methods L{rhumbLine} and L{RhumbLineAux.Intersecant2}
|
|
1256
|
+
or L{RhumbLine.Intersecant2}.
|
|
1257
|
+
|
|
1258
|
+
@return: 2-Tuple of the intersection points (representing a chord),
|
|
1259
|
+
each an instance of this class. Both points are the same
|
|
1260
|
+
instance if the rhumb line is tangent to the circle.
|
|
1261
|
+
|
|
1262
|
+
@raise IntersectionError: The circle and rhumb line do not intersect.
|
|
1263
|
+
|
|
1264
|
+
@raise TypeError: If B{C{point}} is not this C{LatLon} or B{C{circle}}
|
|
1265
|
+
or B{C{other}} invalid.
|
|
1266
|
+
|
|
1267
|
+
@raise ValueError: Invalid B{C{circle}}, B{C{other}}, B{C{height}}
|
|
1268
|
+
or B{C{exact_radius_wrap}}.
|
|
1269
|
+
|
|
1270
|
+
@see: Methods L{RhumbLineAux.Intersecant2} and L{RhumbLine.Intersecant2}.
|
|
1271
|
+
'''
|
|
1272
|
+
def _kwds3(eps=EPS, tol=_TOL, wrap=False, **kwds):
|
|
1273
|
+
return kwds, wrap, dict(eps=eps, tol=tol)
|
|
1274
|
+
|
|
1275
|
+
exact_radius, w, eps_tol = _kwds3(**exact_radius_wrap_eps_tol)
|
|
1276
|
+
|
|
1277
|
+
p = _unrollon(self, self.others(point=point), wrap=w)
|
|
1278
|
+
try:
|
|
1279
|
+
r = Radius_(circle=circle) if _isRadius(circle) else \
|
|
1280
|
+
self.rhumbDistanceTo(self.others(circle=circle), wrap=w, **exact_radius)
|
|
1281
|
+
rl = p.rhumbLine(other, wrap=w, **exact_radius)
|
|
1282
|
+
P, Q = rl.Intersecant2(self.lat, self.lon, r, **eps_tol)
|
|
1283
|
+
|
|
1284
|
+
return self._intersecend2(p, other, w, height, rl.rhumb, P, Q,
|
|
1285
|
+
self.rhumbIntersecant2)
|
|
1286
|
+
|
|
1287
|
+
except (TypeError, ValueError) as x:
|
|
1288
|
+
raise _xError(x, center=self, circle=circle, point=point, other=other,
|
|
1289
|
+
**exact_radius_wrap_eps_tol)
|
|
1290
|
+
|
|
1291
|
+
def rhumbLine(self, other, exact=False, radius=None, wrap=False, **name_caps):
|
|
1292
|
+
'''Get a rhumb line through this point at a given azimuth or through
|
|
1293
|
+
this and an other point.
|
|
1294
|
+
|
|
1295
|
+
@arg other: The azimuth I{of} (compass C{degrees}) or an other point
|
|
1296
|
+
I{on} (this C{LatLon}) the rhumb line.
|
|
1297
|
+
@kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
|
|
1298
|
+
method L{Ellipsoid.rhumb_}.
|
|
1299
|
+
@kwarg radius: Optional earth radius (C{meter}) or earth model
|
|
1300
|
+
(L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}),
|
|
1301
|
+
overriding this point's datum.
|
|
1302
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{other}}
|
|
1303
|
+
point (C{bool}).
|
|
1304
|
+
@kwarg name_caps: Optional C{B{name}=str} and C{caps}, see L{RhumbLine}
|
|
1305
|
+
or L{RhumbLineAux} C{B{caps}}.
|
|
1306
|
+
|
|
1307
|
+
@return: A C{RhumbLine} instance.
|
|
1308
|
+
|
|
1309
|
+
@raise TypeError: Invalid B{C{radius}} or B{C{other}} not C{scalar} nor
|
|
1310
|
+
this C{LatLon}.
|
|
1311
|
+
|
|
1312
|
+
@see: Modules L{rhumb.aux_} and L{rhumb.ekx}.
|
|
1313
|
+
'''
|
|
1314
|
+
r, _, Cs = self._rhumb3(exact, radius)
|
|
1315
|
+
kwds = _xkwds(name_caps, name=self.name, caps=Cs.LINE_OFF)
|
|
1316
|
+
rl = r._DirectLine( self, other, **kwds) if _isDegrees(other) else \
|
|
1317
|
+
r._InverseLine(self, self.others(other), wrap, **kwds)
|
|
1318
|
+
return rl
|
|
1319
|
+
|
|
1320
|
+
def rhumbMidpointTo(self, other, exact=False, radius=None,
|
|
1321
|
+
height=None, fraction=_0_5, wrap=False):
|
|
1322
|
+
'''Return the (loxodromic) midpoint on the rhumb line between this and
|
|
1323
|
+
an other point.
|
|
1324
|
+
|
|
1325
|
+
@arg other: The other point (this C{LatLon}).
|
|
1326
|
+
@kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
|
|
1327
|
+
method L{Ellipsoid.rhumb_}.
|
|
1328
|
+
@kwarg radius: Optional earth radius (C{meter}) or earth model (L{Datum},
|
|
1329
|
+
L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}), overriding
|
|
1330
|
+
this point's datum.
|
|
1331
|
+
@kwarg height: Optional height, overriding the mean height (C{meter}).
|
|
1332
|
+
@kwarg fraction: Midpoint location from this point (C{scalar}), 0 for this,
|
|
1333
|
+
1 for the B{C{other}}, 0.5 for halfway between this and
|
|
1334
|
+
the B{C{other}} point, may be negative or greater than 1.
|
|
1335
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{other}}
|
|
1336
|
+
point (C{bool}).
|
|
1337
|
+
|
|
1338
|
+
@return: The midpoint at the given B{C{fraction}} along the rhumb line
|
|
1339
|
+
(this C{LatLon}).
|
|
1340
|
+
|
|
1341
|
+
@raise TypeError: The B{C{other}} point is incompatible or B{C{radius}}
|
|
1342
|
+
is invalid.
|
|
1343
|
+
|
|
1344
|
+
@raise ValueError: Invalid B{C{height}} or B{C{fraction}}.
|
|
1345
|
+
'''
|
|
1346
|
+
r, D, _ = self._rhumb3(exact, radius)
|
|
1347
|
+
f = Scalar(fraction=fraction)
|
|
1348
|
+
d = r._Inverse(self, self.others(other), wrap) # C.AZIMUTH_DISTANCE
|
|
1349
|
+
d = r._Direct( self, d.azi12, d.s12 * f)
|
|
1350
|
+
h = self._havg(other, f=f, h=height)
|
|
1351
|
+
return self.classof(d.lat2, d.lon2, datum=D, height=h)
|
|
1352
|
+
|
|
1353
|
+
@property_RO
|
|
1354
|
+
def sphericalLatLon(self):
|
|
1355
|
+
'''Get the C{LatLon type} iff spherical, overloaded in L{LatLonSphericalBase}.
|
|
1356
|
+
'''
|
|
1357
|
+
return False
|
|
1358
|
+
|
|
1359
|
+
def thomasTo(self, other, wrap=False):
|
|
1360
|
+
'''Compute the distance between this and an other point using
|
|
1361
|
+
U{Thomas'<https://apps.DTIC.mil/dtic/tr/fulltext/u2/703541.pdf>}
|
|
1362
|
+
formula.
|
|
1363
|
+
|
|
1364
|
+
@arg other: The other point (C{LatLon}).
|
|
1365
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
1366
|
+
the B{C{other}} point (C{bool}).
|
|
1367
|
+
|
|
1368
|
+
@return: Distance (C{meter}, same units as the axes of
|
|
1369
|
+
this point's datum ellipsoid).
|
|
1370
|
+
|
|
1371
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
1372
|
+
|
|
1373
|
+
@see: Function L{pygeodesy.thomas} and methods L{cosineAndoyerLambertTo},
|
|
1374
|
+
L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
|
|
1375
|
+
L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
|
|
1376
|
+
L{flatPolarTo}, L{haversineTo} and L{vincentysTo}.
|
|
1377
|
+
'''
|
|
1378
|
+
return self._distanceTo_(self._formy.thomas_, other, wrap=wrap)
|
|
1379
|
+
|
|
1380
|
+
@deprecated_method
|
|
1381
|
+
def to2ab(self): # PYCHOK no cover
|
|
1382
|
+
'''DEPRECATED, use property L{philam}.'''
|
|
1383
|
+
return self.philam
|
|
1384
|
+
|
|
1385
|
+
def toCartesian(self, height=None, Cartesian=None, **Cartesian_kwds):
|
|
1386
|
+
'''Convert this point to cartesian, I{geocentric} coordinates,
|
|
1387
|
+
also known as I{Earth-Centered, Earth-Fixed} (ECEF).
|
|
1388
|
+
|
|
1389
|
+
@kwarg height: Optional height, overriding this point's height
|
|
1390
|
+
(C{meter}, conventionally).
|
|
1391
|
+
@kwarg Cartesian: Optional class to return the geocentric
|
|
1392
|
+
coordinates (C{Cartesian}) or C{None}.
|
|
1393
|
+
@kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}}
|
|
1394
|
+
keyword arguments, ignored if
|
|
1395
|
+
C{B{Cartesian} is None}.
|
|
1396
|
+
|
|
1397
|
+
@return: A B{C{Cartesian}} or if B{C{Cartesian}} is C{None},
|
|
1398
|
+
an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
|
|
1399
|
+
datum)} with C{C=0} and C{M} if available.
|
|
1400
|
+
|
|
1401
|
+
@raise TypeError: Invalid B{C{Cartesian}} or B{C{Cartesian_kwds}}.
|
|
1402
|
+
|
|
1403
|
+
@see: Methods C{toNvector}, C{toVector} and C{toVector3d}.
|
|
1404
|
+
'''
|
|
1405
|
+
r = self._ecef9 if height is None else self.toEcef(height=height)
|
|
1406
|
+
if Cartesian is not None: # class or .classof
|
|
1407
|
+
r = Cartesian(r, **_xkwds(Cartesian_kwds, name=self.name))
|
|
1408
|
+
_xdatum(r.datum, self.datum)
|
|
1409
|
+
return r
|
|
1410
|
+
|
|
1411
|
+
def _toCartesianEcef(self, height=None, i=None, up=2, **name_point):
|
|
1412
|
+
'''(INTERNAL) Convert to cartesian and check Ecef's before and after.
|
|
1413
|
+
'''
|
|
1414
|
+
p = self.others(up=up, **name_point)
|
|
1415
|
+
c = p.toCartesian(height=height)
|
|
1416
|
+
E = self.Ecef
|
|
1417
|
+
if E:
|
|
1418
|
+
for p in (p, c):
|
|
1419
|
+
e = _xattr(p, Ecef=None)
|
|
1420
|
+
if e not in (None, E): # PYCHOK no cover
|
|
1421
|
+
n = Fmt.INDEX(_xkwds_item2(name_point)[0], i)
|
|
1422
|
+
raise _ValueError(n, e, txt=_incompatible(E.__name__))
|
|
1423
|
+
return c
|
|
1424
|
+
|
|
1425
|
+
def toDatum(self, datum2, height=None, name=NN):
|
|
1426
|
+
'''I{Must be overloaded}.'''
|
|
1427
|
+
notOverloaded(self, datum2, height=height, name=name)
|
|
1428
|
+
|
|
1429
|
+
def toEcef(self, height=None, M=False):
|
|
1430
|
+
'''Convert this point to I{geocentric} coordinates, also known as
|
|
1431
|
+
I{Earth-Centered, Earth-Fixed} (U{ECEF<https://WikiPedia.org/wiki/ECEF>}).
|
|
1432
|
+
|
|
1433
|
+
@kwarg height: Optional height, overriding this point's height
|
|
1434
|
+
(C{meter}, conventionally).
|
|
1435
|
+
@kwarg M: Optionally, include the rotation L{EcefMatrix} (C{bool}).
|
|
1436
|
+
|
|
1437
|
+
@return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
|
|
1438
|
+
with C{C=0} and C{M} if available.
|
|
1439
|
+
|
|
1440
|
+
@raise EcefError: A C{.datum} or an ECEF issue.
|
|
1441
|
+
'''
|
|
1442
|
+
return self._ecef9 if height in (None, self.height) else \
|
|
1443
|
+
self._Ecef_forward(self.lat, self.lon, height=height, M=M)
|
|
1444
|
+
|
|
1445
|
+
@deprecated_method
|
|
1446
|
+
def to3llh(self, height=None): # PYCHOK no cover
|
|
1447
|
+
'''DEPRECATED, use property L{latlonheight} or C{latlon.to3Tuple(B{height})}.'''
|
|
1448
|
+
return self.latlonheight if height in (None, self.height) else \
|
|
1449
|
+
self.latlon.to3Tuple(height)
|
|
1450
|
+
|
|
1451
|
+
def toLocal(self, Xyz=None, ltp=None, **Xyz_kwds):
|
|
1452
|
+
'''Convert this I{geodetic} point to I{local} C{X}, C{Y} and C{Z}.
|
|
1453
|
+
|
|
1454
|
+
@kwarg Xyz: Optional class to return C{X}, C{Y} and C{Z}
|
|
1455
|
+
(L{XyzLocal}, L{Enu}, L{Ned}) or C{None}.
|
|
1456
|
+
@kwarg ltp: The I{local tangent plane} (LTP) to use,
|
|
1457
|
+
overriding this point's LTP (L{Ltp}).
|
|
1458
|
+
@kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
|
|
1459
|
+
arguments, ignored if C{B{Xyz} is None}.
|
|
1460
|
+
|
|
1461
|
+
@return: An B{C{Xyz}} instance or if C{B{Xyz} is None},
|
|
1462
|
+
a L{Local9Tuple}C{(x, y, z, lat, lon, height,
|
|
1463
|
+
ltp, ecef, M)} with C{M=None}, always.
|
|
1464
|
+
|
|
1465
|
+
@raise TypeError: Invalid B{C{ltp}}.
|
|
1466
|
+
'''
|
|
1467
|
+
p = self._ltp._xLtp(ltp, self._Ltp)
|
|
1468
|
+
return p._ecef2local(self._ecef9, Xyz, Xyz_kwds)
|
|
1469
|
+
|
|
1470
|
+
def toLtp(self, Ecef=None):
|
|
1471
|
+
'''Return the I{local tangent plane} (LTP) for this point.
|
|
1472
|
+
|
|
1473
|
+
@kwarg Ecef: Optional ECEF I{class} (L{EcefKarney}, ...
|
|
1474
|
+
L{EcefYou}), overriding this point's C{Ecef}.
|
|
1475
|
+
'''
|
|
1476
|
+
return self._Ltp if Ecef in (None, self.Ecef) else self._ltp.Ltp(
|
|
1477
|
+
self, ecef=Ecef(self.datum), name=self.name)
|
|
1478
|
+
|
|
1479
|
+
def toNormal(self, deep=False, name=NN):
|
|
1480
|
+
'''Get this point I{normalized} to C{abs(lat) <= 90}
|
|
1481
|
+
and C{abs(lon) <= 180}.
|
|
1482
|
+
|
|
1483
|
+
@kwarg deep: If C{True} make a deep, otherwise a
|
|
1484
|
+
shallow copy (C{bool}).
|
|
1485
|
+
@kwarg name: Optional name of the copy (C{str}).
|
|
1486
|
+
|
|
1487
|
+
@return: A copy of this point, I{normalized} and
|
|
1488
|
+
optionally renamed (C{LatLon}).
|
|
1489
|
+
|
|
1490
|
+
@see: Property L{isnormal}, method L{normal} and function
|
|
1491
|
+
L{pygeodesy.normal}.
|
|
1492
|
+
'''
|
|
1493
|
+
ll = self.copy(deep=deep)
|
|
1494
|
+
_ = ll.normal()
|
|
1495
|
+
if name:
|
|
1496
|
+
ll.rename(name)
|
|
1497
|
+
return ll
|
|
1498
|
+
|
|
1499
|
+
def toNvector(self, h=None, Nvector=None, **Nvector_kwds):
|
|
1500
|
+
'''Convert this point to C{n-vector} (normal to the earth's surface)
|
|
1501
|
+
components, I{including height}.
|
|
1502
|
+
|
|
1503
|
+
@kwarg h: Optional height, overriding this point's height (C{meter}).
|
|
1504
|
+
@kwarg Nvector: Optional class to return the C{n-vector} components
|
|
1505
|
+
(C{Nvector}) or C{None}.
|
|
1506
|
+
@kwarg Nvector_kwds: Optional, additional B{C{Nvector}} keyword
|
|
1507
|
+
arguments, ignored if C{B{Nvector} is None}.
|
|
1508
|
+
|
|
1509
|
+
@return: An B{C{Nvector}} or a L{Vector4Tuple}C{(x, y, z, h)} if
|
|
1510
|
+
B{C{Nvector}} is C{None}.
|
|
1511
|
+
|
|
1512
|
+
@raise TypeError: Invalid B{C{h}}, B{C{Nvector}} or B{C{Nvector_kwds}}
|
|
1513
|
+
item.
|
|
1514
|
+
|
|
1515
|
+
@see: Methods C{toCartesian}, C{toVector} and C{toVector3d}.
|
|
1516
|
+
'''
|
|
1517
|
+
h = self._heigHt(h)
|
|
1518
|
+
if Nvector is None:
|
|
1519
|
+
r = self._n_xyz3.to4Tuple(h)
|
|
1520
|
+
else:
|
|
1521
|
+
x, y, z = self._n_xyz3
|
|
1522
|
+
r = Nvector(x, y, z, h=h, ll=self, **_xkwds(Nvector_kwds, name=self.name))
|
|
1523
|
+
return r
|
|
1524
|
+
|
|
1525
|
+
def toStr(self, form=F_DMS, joined=_COMMASPACE_, m=_m_, **prec_sep_s_D_M_S): # PYCHOK expected
|
|
1526
|
+
'''Convert this point to a "lat, lon[, +/-height]" string, formatted
|
|
1527
|
+
in the given C{B{form}at}.
|
|
1528
|
+
|
|
1529
|
+
@kwarg form: The lat-/longitude C{B{form}at} to use (C{str}), see
|
|
1530
|
+
functions L{pygeodesy.latDMS} or L{pygeodesy.lonDMS}.
|
|
1531
|
+
@kwarg joined: Separator to join the lat-, longitude and heigth
|
|
1532
|
+
strings (C{str} or C{None} or C{NN} for non-joined).
|
|
1533
|
+
@kwarg m: Optional unit of the height (C{str}), use C{None} to
|
|
1534
|
+
exclude height from the returned string.
|
|
1535
|
+
@kwarg prec_sep_s_D_M_S: Optional C{B{prec}ision}, C{B{sep}arator},
|
|
1536
|
+
B{C{s_D}}, B{C{s_M}}, B{C{s_S}} and B{C{s_DMS}} keyword
|
|
1537
|
+
arguments, see function L{pygeodesy.latDMS} or
|
|
1538
|
+
L{pygeodesy.lonDMS}.
|
|
1539
|
+
|
|
1540
|
+
@return: This point in the specified C{B{form}at}, etc. (C{str} or
|
|
1541
|
+
a 2- or 3-tuple C{(lat_str, lon_str[, height_str])} if
|
|
1542
|
+
C{B{joined}=NN} or C{B{joined}=None}).
|
|
1543
|
+
|
|
1544
|
+
@see: Function L{pygeodesy.latDMS} or L{pygeodesy.lonDMS} for more
|
|
1545
|
+
details about keyword arguments C{B{form}at}, C{B{prec}ision},
|
|
1546
|
+
C{B{sep}arator}, B{C{s_D}}, B{C{s_M}}, B{C{s_S}} and B{C{s_DMS}}.
|
|
1547
|
+
'''
|
|
1548
|
+
t = (latDMS(self.lat, form=form, **prec_sep_s_D_M_S),
|
|
1549
|
+
lonDMS(self.lon, form=form, **prec_sep_s_D_M_S))
|
|
1550
|
+
if self.height and m is not None:
|
|
1551
|
+
t += (self.heightStr(m=m),)
|
|
1552
|
+
return joined.join(t) if joined else t
|
|
1553
|
+
|
|
1554
|
+
def toVector(self, Vector=None, **Vector_kwds):
|
|
1555
|
+
'''Convert this point to a C{Vector} with the I{geocentric} C{(x,
|
|
1556
|
+
y, z)} (ECEF) coordinates, I{ignoring height}.
|
|
1557
|
+
|
|
1558
|
+
@kwarg Vector: Optional class to return the I{geocentric}
|
|
1559
|
+
components (L{Vector3d}) or C{None}.
|
|
1560
|
+
@kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword
|
|
1561
|
+
arguments, ignored if C{B{Vector} is None}.
|
|
1562
|
+
|
|
1563
|
+
@return: A B{C{Vector}} or a L{Vector3Tuple}C{(x, y, z)}
|
|
1564
|
+
if B{C{Vector}} is C{None}.
|
|
1565
|
+
|
|
1566
|
+
@raise TypeError: Invalid B{C{Vector}} or B{C{Vector_kwds}} item.
|
|
1567
|
+
|
|
1568
|
+
@see: Methods C{toCartesian}, C{toNvector} and C{toVector3d}.
|
|
1569
|
+
'''
|
|
1570
|
+
return self._ecef9.toVector(Vector=Vector, **Vector_kwds)
|
|
1571
|
+
|
|
1572
|
+
def toVector3d(self, norm=True, **Vector3d_kwds):
|
|
1573
|
+
'''Convert this point to a L{Vector3d} with the I{geocentric} C{(x,
|
|
1574
|
+
y, z)} (ECEF) I{unit} coordinates, I{ignoring height}.
|
|
1575
|
+
|
|
1576
|
+
@kwarg norm: Normalize the 3-D vector (C{bool}).
|
|
1577
|
+
@kwarg Vector3d_kwds: Optional L{Vector3d} keyword arguments.
|
|
1578
|
+
|
|
1579
|
+
@return: Unit vector (L{Vector3d}).
|
|
1580
|
+
|
|
1581
|
+
@raise TypeError: Invalid B{C{Vector3d_kwds}} item.
|
|
1582
|
+
|
|
1583
|
+
@see: Methods C{toCartesian}, C{toNvector} and C{toVector}.
|
|
1584
|
+
'''
|
|
1585
|
+
r = self.toVector(Vector=Vector3d, **Vector3d_kwds)
|
|
1586
|
+
if norm:
|
|
1587
|
+
r = r.unit(ll=self)
|
|
1588
|
+
return r
|
|
1589
|
+
|
|
1590
|
+
def toWm(self, **toWm_kwds):
|
|
1591
|
+
'''Convert this point to a WM coordinate.
|
|
1592
|
+
|
|
1593
|
+
@kwarg toWm_kwds: Optional L{pygeodesy.toWm} keyword arguments.
|
|
1594
|
+
|
|
1595
|
+
@return: The WM coordinate (L{Wm}).
|
|
1596
|
+
|
|
1597
|
+
@see: Function L{pygeodesy.toWm}.
|
|
1598
|
+
'''
|
|
1599
|
+
return self._wm if not toWm_kwds else _MODS.webmercator.toWm(
|
|
1600
|
+
self, **_xkwds(toWm_kwds, name=self.name))
|
|
1601
|
+
|
|
1602
|
+
@deprecated_method
|
|
1603
|
+
def to3xyz(self): # PYCHOK no cover
|
|
1604
|
+
'''DEPRECATED, use property L{xyz} or method L{toNvector}, L{toVector},
|
|
1605
|
+
L{toVector3d} or perhaps (geocentric) L{toEcef}.'''
|
|
1606
|
+
return self.xyz # self.toVector()
|
|
1607
|
+
|
|
1608
|
+
def vincentysTo(self, other, **radius_wrap):
|
|
1609
|
+
'''Compute the distance between this and an other point using
|
|
1610
|
+
U{Vincenty's<https://WikiPedia.org/wiki/Great-circle_distance>}
|
|
1611
|
+
spherical formula.
|
|
1612
|
+
|
|
1613
|
+
@arg other: The other point (C{LatLon}).
|
|
1614
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
1615
|
+
L{pygeodesy.vincentys}, overriding the
|
|
1616
|
+
default mean C{radius} of this point's
|
|
1617
|
+
datum ellipsoid.
|
|
1618
|
+
|
|
1619
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
1620
|
+
|
|
1621
|
+
@raise TypeError: The B{C{other}} point is not C{LatLon}.
|
|
1622
|
+
|
|
1623
|
+
@see: Function L{pygeodesy.vincentys} and methods L{cosineAndoyerLambertTo},
|
|
1624
|
+
L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
|
|
1625
|
+
L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
|
|
1626
|
+
L{flatPolarTo}, L{haversineTo} and L{thomasTo}.
|
|
1627
|
+
'''
|
|
1628
|
+
return self._distanceTo(self._formy.vincentys, other, **_xkwds(radius_wrap, radius=None))
|
|
1629
|
+
|
|
1630
|
+
@Property_RO
|
|
1631
|
+
def _wm(self):
|
|
1632
|
+
'''(INTERNAL) Get this point as webmercator (L{Wm}).
|
|
1633
|
+
'''
|
|
1634
|
+
return _MODS.webmercator.toWm(self)
|
|
1635
|
+
|
|
1636
|
+
@property_RO
|
|
1637
|
+
def xyz(self):
|
|
1638
|
+
'''Get the I{geocentric} C{(x, y, z)} coordinates (L{Vector3Tuple}C{(x, y, z)})
|
|
1639
|
+
'''
|
|
1640
|
+
return self._ecef9.xyz
|
|
1641
|
+
|
|
1642
|
+
@Property_RO
|
|
1643
|
+
def xyzh(self):
|
|
1644
|
+
'''Get the I{geocentric} C{(x, y, z)} coordinates and height (L{Vector4Tuple}C{(x, y, z, h)})
|
|
1645
|
+
'''
|
|
1646
|
+
return self.xyz.to4Tuple(self.height)
|
|
1647
|
+
|
|
1648
|
+
|
|
1649
|
+
class _toCartesian3(object): # see also .formy._idllmn6, .geodesicw._wargs, .vector2d._numpy
|
|
1650
|
+
'''(INTERNAL) Wrapper to convert 2 other points.
|
|
1651
|
+
'''
|
|
1652
|
+
@contextmanager # <https://www.Python.org/dev/peps/pep-0343/> Examples
|
|
1653
|
+
def __call__(self, p, p2, p3, wrap, **kwds):
|
|
1654
|
+
try:
|
|
1655
|
+
if wrap:
|
|
1656
|
+
p2, p3 = map1(_Wrap.point, p2, p3)
|
|
1657
|
+
kwds = _xkwds(kwds, wrap=wrap)
|
|
1658
|
+
yield (p. toCartesian().copy(name=_point_), # copy to rename
|
|
1659
|
+
p._toCartesianEcef(up=4, point2=p2),
|
|
1660
|
+
p._toCartesianEcef(up=4, point3=p3))
|
|
1661
|
+
except (AssertionError, TypeError, ValueError) as x: # Exception?
|
|
1662
|
+
raise _xError(x, point=p, point2=p2, point3=p3, **kwds)
|
|
1663
|
+
|
|
1664
|
+
_toCartesian3 = _toCartesian3() # PYCHOK singleton
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
def _latlonheight3(latlonh, height, wrap): # in .points.LatLon_.__init__
|
|
1668
|
+
'''(INTERNAL) Get 3-tuple C{(lat, lon, height)}.
|
|
1669
|
+
'''
|
|
1670
|
+
try:
|
|
1671
|
+
lat, lon = latlonh.lat, latlonh.lon
|
|
1672
|
+
height = _xattr(latlonh, height=height)
|
|
1673
|
+
except AttributeError:
|
|
1674
|
+
raise _IsnotError(_LatLon_, latlonh=latlonh)
|
|
1675
|
+
if wrap:
|
|
1676
|
+
lat, lon = _Wrap.latlon(lat, lon)
|
|
1677
|
+
return lat, lon, height
|
|
1678
|
+
|
|
1679
|
+
|
|
1680
|
+
def _trilaterate5(p1, d1, p2, d2, p3, d3, area=True, eps=EPS1, # MCCABE 13
|
|
1681
|
+
radius=R_M, wrap=False):
|
|
1682
|
+
'''(INTERNAL) Trilaterate three points by I{area overlap} or by
|
|
1683
|
+
I{perimeter intersection} of three circles.
|
|
1684
|
+
|
|
1685
|
+
@note: The B{C{radius}} is only needed for the n-vectorial and
|
|
1686
|
+
C{sphericalTrigonometry.LatLon.distanceTo} methods and
|
|
1687
|
+
silently ignored by the C{ellipsoidalExact}, C{-GeodSolve},
|
|
1688
|
+
C{-Karney} and C{-Vincenty.LatLon.distanceTo} methods.
|
|
1689
|
+
'''
|
|
1690
|
+
p2, p3, w = _unrollon3(p1, p2, p3, wrap)
|
|
1691
|
+
|
|
1692
|
+
r1 = Distance_(distance1=d1)
|
|
1693
|
+
r2 = Distance_(distance2=d2)
|
|
1694
|
+
r3 = Distance_(distance3=d3)
|
|
1695
|
+
m = 0 if area else (r1 + r2 + r3)
|
|
1696
|
+
pc = 0
|
|
1697
|
+
t = []
|
|
1698
|
+
for _ in range(3):
|
|
1699
|
+
try: # intersection of circle (p1, r1) and (p2, r2)
|
|
1700
|
+
c1, c2 = p1.intersections2(r1, p2, r2, wrap=w)
|
|
1701
|
+
|
|
1702
|
+
if area: # check overlap
|
|
1703
|
+
if c1 is c2: # abutting
|
|
1704
|
+
c = c1
|
|
1705
|
+
else: # nearest point on radical
|
|
1706
|
+
c = p3.nearestOn(c1, c2, within=True, wrap=w)
|
|
1707
|
+
d = r3 - p3.distanceTo(c, radius=radius, wrap=w)
|
|
1708
|
+
if d > eps: # sufficient overlap
|
|
1709
|
+
t.append((d, c))
|
|
1710
|
+
m = max(m, d)
|
|
1711
|
+
|
|
1712
|
+
else: # check intersection
|
|
1713
|
+
for c in ((c1,) if c1 is c2 else (c1, c2)):
|
|
1714
|
+
d = fabs(r3 - p3.distanceTo(c, radius=radius, wrap=w))
|
|
1715
|
+
if d < eps: # below margin
|
|
1716
|
+
t.append((d, c))
|
|
1717
|
+
m = min(m, d)
|
|
1718
|
+
|
|
1719
|
+
except IntersectionError as x:
|
|
1720
|
+
if _concentric_ in str(x): # XXX ConcentricError?
|
|
1721
|
+
pc += 1
|
|
1722
|
+
|
|
1723
|
+
p1, r1, p2, r2, p3, r3 = p2, r2, p3, r3, p1, r1 # rotate
|
|
1724
|
+
|
|
1725
|
+
if t: # get min, max, points and count ...
|
|
1726
|
+
t = tuple(sorted(t))
|
|
1727
|
+
n = len(t), # as 1-tuple
|
|
1728
|
+
# ... or for a single trilaterated result,
|
|
1729
|
+
# min *is* max, min- *is* maxPoint and n=1, 2 or 3
|
|
1730
|
+
return Trilaterate5Tuple(t[0] + t[-1] + n) # *(t[0] + ...)
|
|
1731
|
+
|
|
1732
|
+
elif area and pc == 3: # all pairwise concentric ...
|
|
1733
|
+
r, p = min((r1, p1), (r2, p2), (r3, p3))
|
|
1734
|
+
m = max(r1, r2, r3)
|
|
1735
|
+
# ... return "smallest" point twice, the smallest
|
|
1736
|
+
# and largest distance and n=0 for concentric
|
|
1737
|
+
return Trilaterate5Tuple(float(r), p, float(m), p, 0)
|
|
1738
|
+
|
|
1739
|
+
n, f = (_overlap_, max) if area else (_intersection_, min)
|
|
1740
|
+
t = _COMMASPACE_(_no_(n), '%s %.3g' % (f.__name__, m))
|
|
1741
|
+
raise IntersectionError(area=area, eps=eps, wrap=wrap, txt=t)
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
__all__ += _ALL_DOCS(LatLonBase)
|
|
1745
|
+
|
|
1746
|
+
# **) MIT License
|
|
1747
|
+
#
|
|
1748
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
1749
|
+
#
|
|
1750
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
1751
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
1752
|
+
# to deal in the Software without restriction, including without limitation
|
|
1753
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
1754
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
1755
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
1756
|
+
#
|
|
1757
|
+
# The above copyright notice and this permission notice shall be included
|
|
1758
|
+
# in all copies or substantial portions of the Software.
|
|
1759
|
+
#
|
|
1760
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
1761
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1762
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
1763
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
1764
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
1765
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
1766
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|