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,1382 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''A pure Python version of I{Karney}'s C++ class U{GeodesicExact
|
|
5
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>}.
|
|
6
|
+
|
|
7
|
+
Class L{GeodesicExact} follows the naming, methods and return values
|
|
8
|
+
of class C{Geodesic} from I{Karney}'s Python U{geographiclib
|
|
9
|
+
<https://GitHub.com/geographiclib/geographiclib-python>}.
|
|
10
|
+
|
|
11
|
+
Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2023)
|
|
12
|
+
and licensed under the MIT/X11 License. For more information, see the
|
|
13
|
+
U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
|
|
14
|
+
'''
|
|
15
|
+
# make sure int/int division yields float quotient
|
|
16
|
+
from __future__ import division as _; del _ # PYCHOK semicolon
|
|
17
|
+
|
|
18
|
+
# A copy of comments from Karney's C{GeodesicExact.cpp}:
|
|
19
|
+
#
|
|
20
|
+
# This is a reformulation of the geodesic problem. The
|
|
21
|
+
# notation is as follows:
|
|
22
|
+
# - at a general point (no suffix or 1 or 2 as suffix)
|
|
23
|
+
# - phi = latitude
|
|
24
|
+
# - lambda = longitude
|
|
25
|
+
# - beta = latitude on auxiliary sphere
|
|
26
|
+
# - omega = longitude on auxiliary sphere
|
|
27
|
+
# - alpha = azimuth of great circle
|
|
28
|
+
# - sigma = arc length along great circle
|
|
29
|
+
# - s = distance
|
|
30
|
+
# - tau = scaled distance (= sigma at multiples of PI/2)
|
|
31
|
+
# - at northwards equator crossing
|
|
32
|
+
# - beta = phi = 0
|
|
33
|
+
# - omega = lambda = 0
|
|
34
|
+
# - alpha = alpha0
|
|
35
|
+
# - sigma = s = 0
|
|
36
|
+
# - a 12 suffix means a difference, e.g., s12 = s2 - s1.
|
|
37
|
+
# - s and c prefixes mean sin and cos
|
|
38
|
+
|
|
39
|
+
from pygeodesy.basics import _copysign, _xinstanceof, _xor, unsigned0
|
|
40
|
+
from pygeodesy.constants import EPS, EPS0, EPS02, MANT_DIG, NAN, PI, _EPSqrt, \
|
|
41
|
+
_SQRT2_2, isnan, _0_0, _0_001, _0_01, _0_1, _0_5, \
|
|
42
|
+
_1_0, _N_1_0, _1_75, _2_0, _N_2_0, _2__PI, _3_0, \
|
|
43
|
+
_4_0, _6_0, _8_0, _16_0, _90_0, _180_0, _1000_0
|
|
44
|
+
from pygeodesy.datums import _earth_datum, _WGS84, _EWGS84
|
|
45
|
+
# from pygeodesy.ellipsoids import _EWGS84 # from .datums
|
|
46
|
+
from pygeodesy.fmath import hypot as _hypot
|
|
47
|
+
from pygeodesy.fsums import fsumf_, fsum1f_
|
|
48
|
+
from pygeodesy.geodesicx.gxbases import _cosSeries, _GeodesicBase, \
|
|
49
|
+
_sincos12, _sin1cos2, _xnC4
|
|
50
|
+
from pygeodesy.geodesicx.gxline import _GeodesicLineExact, _TINY, _update_glXs
|
|
51
|
+
from pygeodesy.interns import NN, _COMMASPACE_, _DOT_, _UNDER_
|
|
52
|
+
from pygeodesy.karney import _around, _atan2d, Caps, _cbrt, _diff182, \
|
|
53
|
+
_fix90, GDict, GeodesicError, _K_2_0, \
|
|
54
|
+
_norm2, _norm180, _polynomial, _signBit, \
|
|
55
|
+
_sincos2, _sincos2d, _sincos2de, _unsigned2
|
|
56
|
+
from pygeodesy.lazily import _ALL_DOCS, _ALL_MODS as _MODS
|
|
57
|
+
from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple
|
|
58
|
+
from pygeodesy.props import deprecated_Property, Property, Property_RO, property_RO
|
|
59
|
+
from pygeodesy.streprs import Fmt, pairs
|
|
60
|
+
from pygeodesy.utily import atan2d as _atan2d_reverse, _unrollon, _Wrap, wrap360
|
|
61
|
+
|
|
62
|
+
from math import atan2, copysign, cos, degrees, fabs, radians, sqrt
|
|
63
|
+
|
|
64
|
+
__all__ = ()
|
|
65
|
+
__version__ = '24.02.21'
|
|
66
|
+
|
|
67
|
+
_MAXIT1 = 20
|
|
68
|
+
_MAXIT2 = 10 + _MAXIT1 + MANT_DIG # MANT_DIG == C++ digits
|
|
69
|
+
|
|
70
|
+
# increased multiplier in defn of _TOL1 from 100 to 200 to fix Inverse
|
|
71
|
+
# case 52.784459512564 0 -52.784459512563990912 179.634407464943777557
|
|
72
|
+
# which otherwise failed for Visual Studio 10 (Release and Debug)
|
|
73
|
+
_TOL0 = EPS
|
|
74
|
+
_TOL1 = _TOL0 * -200 # negative
|
|
75
|
+
_TOL2 = _EPSqrt # == sqrt(_TOL0)
|
|
76
|
+
_TOL3 = _TOL2 * _0_1
|
|
77
|
+
_TOLb = _TOL2 * _TOL0 # Check on bisection interval
|
|
78
|
+
_THR1 = _TOL2 * _1000_0 + _1_0
|
|
79
|
+
|
|
80
|
+
_TINY3 = _TINY * _3_0
|
|
81
|
+
_TOL08 = _TOL0 * _8_0
|
|
82
|
+
_TOL016 = _TOL0 * _16_0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _atan12(*sincos12, **sineg0):
|
|
86
|
+
'''(INTERNAL) Return C{ang12} in C{radians}.
|
|
87
|
+
'''
|
|
88
|
+
return atan2(*_sincos12(*sincos12, **sineg0))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _eTOL2(f):
|
|
92
|
+
# Using the auxiliary sphere solution with dnm computed at
|
|
93
|
+
# (bet1 + bet2) / 2, the relative error in the azimuth
|
|
94
|
+
# consistency check is sig12^2 * abs(f) * min(1, 1-f/2) / 2.
|
|
95
|
+
# (Error measured for 1/100 < b/a < 100 and abs(f) >= 1/1000.
|
|
96
|
+
|
|
97
|
+
# For a given f and sig12, the max error occurs for lines
|
|
98
|
+
# near the pole. If the old rule for computing dnm = (dn1
|
|
99
|
+
# + dn2)/2 is used, then the error increases by a factor of
|
|
100
|
+
# 2.) Setting this equal to epsilon gives sig12 = etol2.
|
|
101
|
+
|
|
102
|
+
# Here 0.1 is a safety factor (error decreased by 100) and
|
|
103
|
+
# max(0.001, abs(f)) stops etol2 getting too large in the
|
|
104
|
+
# nearly spherical case.
|
|
105
|
+
t = min(_1_0, _1_0 - f * _0_5) * max(_0_001, fabs(f)) * _0_5
|
|
106
|
+
return _TOL3 / (sqrt(t) if t > EPS02 else EPS0)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class _PDict(GDict):
|
|
110
|
+
'''(INTERNAL) Parameters passed around in C{._GDictInverse} and
|
|
111
|
+
optionally returned when C{GeodesicExact.debug} is C{True}.
|
|
112
|
+
'''
|
|
113
|
+
def set_sigs(self, ssig1, csig1, ssig2, csig2):
|
|
114
|
+
'''Update the C{sig1} and C{sig2} parameters.
|
|
115
|
+
'''
|
|
116
|
+
self.set_(ssig1=ssig1, csig1=csig1, sncndn1=(ssig1, csig1, self.dn1), # PYCHOK dn1
|
|
117
|
+
ssig2=ssig2, csig2=csig2, sncndn2=(ssig2, csig2, self.dn2)) # PYCHOK dn2
|
|
118
|
+
|
|
119
|
+
def toGDict(self): # PYCHOK no cover
|
|
120
|
+
'''Return as C{GDict} without attrs C{sncndn1} and C{sncndn2}.
|
|
121
|
+
'''
|
|
122
|
+
def _rest(sncndn1=None, sncndn2=None, **rest): # PYCHOK sncndn* not used
|
|
123
|
+
return GDict(rest)
|
|
124
|
+
|
|
125
|
+
return _rest(**self)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class GeodesicExact(_GeodesicBase):
|
|
129
|
+
'''A pure Python version of I{Karney}'s C++ class U{GeodesicExact
|
|
130
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>},
|
|
131
|
+
modeled after I{Karney}'s Python class U{geodesic.Geodesic<https://GitHub.com/
|
|
132
|
+
geographiclib/geographiclib-python>}.
|
|
133
|
+
'''
|
|
134
|
+
_datum = _WGS84
|
|
135
|
+
_nC4 = 30 # default C4order
|
|
136
|
+
|
|
137
|
+
def __init__(self, a_ellipsoid=_EWGS84, f=None, name=NN, C4order=None,
|
|
138
|
+
C4Order=None): # for backward compatibility
|
|
139
|
+
'''New L{GeodesicExact} instance.
|
|
140
|
+
|
|
141
|
+
@arg a_ellipsoid: An ellipsoid (L{Ellipsoid}) or datum (L{Datum}) or
|
|
142
|
+
the equatorial radius of the ellipsoid (C{scalar},
|
|
143
|
+
conventionally in C{meter}), see B{C{f}}.
|
|
144
|
+
@arg f: The flattening of the ellipsoid (C{scalar}) if B{C{a_ellipsoid}}
|
|
145
|
+
is specified as C{scalar}.
|
|
146
|
+
@kwarg name: Optional name (C{str}).
|
|
147
|
+
@kwarg C4order: Optional series expansion order (C{int}), see property
|
|
148
|
+
L{C4order}, default C{30}.
|
|
149
|
+
@kwarg C4Order: DEPRECATED, use keyword argument B{C{C4order}}.
|
|
150
|
+
|
|
151
|
+
@raise GeodesicError: Invalid B{C{C4order}}.
|
|
152
|
+
'''
|
|
153
|
+
_earth_datum(self, a_ellipsoid, f=f, name=name)
|
|
154
|
+
if name:
|
|
155
|
+
self.name = name
|
|
156
|
+
if C4order: # XXX private copy, always?
|
|
157
|
+
self.C4order = C4order
|
|
158
|
+
elif C4Order: # for backward compatibility
|
|
159
|
+
self.C4Order = C4Order
|
|
160
|
+
|
|
161
|
+
@Property_RO
|
|
162
|
+
def a(self):
|
|
163
|
+
'''Get the I{equatorial} radius, semi-axis (C{meter}).
|
|
164
|
+
'''
|
|
165
|
+
return self.ellipsoid.a
|
|
166
|
+
|
|
167
|
+
def ArcDirect(self, lat1, lon1, azi1, a12, outmask=Caps.STANDARD):
|
|
168
|
+
'''Solve the I{Direct} geodesic problem in terms of (spherical) arc length.
|
|
169
|
+
|
|
170
|
+
@arg lat1: Latitude of the first point (C{degrees}).
|
|
171
|
+
@arg lon1: Longitude of the first point (C{degrees}).
|
|
172
|
+
@arg azi1: Azimuth at the first point (compass C{degrees}).
|
|
173
|
+
@arg a12: Arc length between the points (C{degrees}), can be negative.
|
|
174
|
+
@kwarg outmask: Bit-or'ed combination of L{Caps} values specifying
|
|
175
|
+
the quantities to be returned.
|
|
176
|
+
|
|
177
|
+
@return: A L{GDict} with up to 12 items C{lat1, lon1, azi1, lat2,
|
|
178
|
+
lon2, azi2, m12, a12, s12, M12, M21, S12} with C{lat1},
|
|
179
|
+
C{lon1}, C{azi1} and arc length C{a12} always included.
|
|
180
|
+
|
|
181
|
+
@see: C++ U{GeodesicExact.ArcDirect
|
|
182
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>}
|
|
183
|
+
and Python U{Geodesic.ArcDirect<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
|
|
184
|
+
'''
|
|
185
|
+
return self._GDictDirect(lat1, lon1, azi1, True, a12, outmask)
|
|
186
|
+
|
|
187
|
+
def ArcDirectLine(self, lat1, lon1, azi1, a12, caps=Caps.ALL, name=NN):
|
|
188
|
+
'''Define a L{GeodesicLineExact} in terms of the I{direct} geodesic problem and as arc length.
|
|
189
|
+
|
|
190
|
+
@arg lat1: Latitude of the first point (C{degrees}).
|
|
191
|
+
@arg lon1: Longitude of the first point (C{degrees}).
|
|
192
|
+
@arg azi1: Azimuth at the first point (compass C{degrees}).
|
|
193
|
+
@arg a12: Arc length between the points (C{degrees}), can be negative.
|
|
194
|
+
@kwarg caps: Bit-or'ed combination of L{Caps} values specifying
|
|
195
|
+
the capabilities the L{GeodesicLineExact} instance
|
|
196
|
+
should possess, i.e., which quantities can be
|
|
197
|
+
returned by calls to L{GeodesicLineExact.Position}
|
|
198
|
+
and L{GeodesicLineExact.ArcPosition}.
|
|
199
|
+
|
|
200
|
+
@return: A L{GeodesicLineExact} instance.
|
|
201
|
+
|
|
202
|
+
@note: The third point of the L{GeodesicLineExact} is set to correspond
|
|
203
|
+
to the second point of the I{Inverse} geodesic problem.
|
|
204
|
+
|
|
205
|
+
@note: Latitude B{C{lat1}} should in the range C{[-90, +90]}.
|
|
206
|
+
|
|
207
|
+
@see: C++ U{GeodesicExact.ArcDirectLine
|
|
208
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
|
|
209
|
+
Python U{Geodesic.ArcDirectLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
|
|
210
|
+
'''
|
|
211
|
+
return self._GenDirectLine(lat1, lon1, azi1, True, a12, caps, name=name)
|
|
212
|
+
|
|
213
|
+
def Area(self, polyline=False, name=NN):
|
|
214
|
+
'''Set up a L{GeodesicAreaExact} to compute area and
|
|
215
|
+
perimeter of a polygon.
|
|
216
|
+
|
|
217
|
+
@kwarg polyline: If C{True} perimeter only, otherwise
|
|
218
|
+
area and perimeter (C{bool}).
|
|
219
|
+
@kwarg name: Optional name (C{str}).
|
|
220
|
+
|
|
221
|
+
@return: A L{GeodesicAreaExact} instance.
|
|
222
|
+
|
|
223
|
+
@note: The B{C{debug}} setting is passed as C{verbose}
|
|
224
|
+
to the returned L{GeodesicAreaExact} instance.
|
|
225
|
+
'''
|
|
226
|
+
gaX = _MODS.geodesicx.GeodesicAreaExact(self, polyline=polyline,
|
|
227
|
+
name=name or self.name)
|
|
228
|
+
if self.debug:
|
|
229
|
+
gaX.verbose = True
|
|
230
|
+
return gaX
|
|
231
|
+
|
|
232
|
+
@Property_RO
|
|
233
|
+
def b(self):
|
|
234
|
+
'''Get the ellipsoid's I{polar} radius, semi-axis (C{meter}).
|
|
235
|
+
'''
|
|
236
|
+
return self.ellipsoid.b
|
|
237
|
+
|
|
238
|
+
@Property_RO
|
|
239
|
+
def c2x(self):
|
|
240
|
+
'''Get the ellipsoid's I{authalic} earth radius I{squared} (C{meter} I{squared}).
|
|
241
|
+
'''
|
|
242
|
+
# The Geodesic class substitutes atanh(sqrt(e2)) for asinh(sqrt(ep2))
|
|
243
|
+
# in the definition of _c2. The latter is more accurate for very
|
|
244
|
+
# oblate ellipsoids (which the Geodesic class does not handle). Of
|
|
245
|
+
# course, the area calculation in GeodesicExact is still based on a
|
|
246
|
+
# series and only holds for moderately oblate (or prolate) ellipsoids.
|
|
247
|
+
return self.ellipsoid.c2x
|
|
248
|
+
|
|
249
|
+
c2 = c2x # in this particular case
|
|
250
|
+
|
|
251
|
+
def C4f(self, eps):
|
|
252
|
+
'''Evaluate the C{C4x} coefficients for B{C{eps}}.
|
|
253
|
+
|
|
254
|
+
@arg eps: Polynomial factor (C{float}).
|
|
255
|
+
|
|
256
|
+
@return: C{C4order}-Tuple of C{C4x(B{eps})} coefficients.
|
|
257
|
+
'''
|
|
258
|
+
def _c4(nC4, C4x):
|
|
259
|
+
i, x, e = 0, _1_0, eps
|
|
260
|
+
_p = _polynomial
|
|
261
|
+
for r in range(nC4, 0, -1):
|
|
262
|
+
j = i + r
|
|
263
|
+
yield _p(e, C4x, i, j) * x
|
|
264
|
+
x *= e
|
|
265
|
+
i = j
|
|
266
|
+
# assert i == (nC4 * (nC4 + 1)) // 2
|
|
267
|
+
|
|
268
|
+
return tuple(_c4(self._nC4, self._C4x))
|
|
269
|
+
|
|
270
|
+
def _C4f_k2(self, k2): # in ._GDictInverse and gxline._GeodesicLineExact._C4a
|
|
271
|
+
'''(INTERNAL) Compute C{eps} from B{C{k2}} and invoke C{C4f}.
|
|
272
|
+
'''
|
|
273
|
+
return self.C4f(k2 / fsumf_(_2_0, sqrt(k2 + _1_0) * _2_0, k2))
|
|
274
|
+
|
|
275
|
+
@Property
|
|
276
|
+
def C4order(self):
|
|
277
|
+
'''Get the series expansion order (C{int}, 24, 27 or 30).
|
|
278
|
+
'''
|
|
279
|
+
return self._nC4
|
|
280
|
+
|
|
281
|
+
@C4order.setter # PYCHOK .setter!
|
|
282
|
+
def C4order(self, order):
|
|
283
|
+
'''Set the series expansion order (C{int}, 24, 27 or 30).
|
|
284
|
+
|
|
285
|
+
@raise GeodesicError: Invalid B{C{order}}.
|
|
286
|
+
'''
|
|
287
|
+
_xnC4(C4order=order)
|
|
288
|
+
if self._nC4 != order:
|
|
289
|
+
GeodesicExact._C4x._update(self)
|
|
290
|
+
_update_glXs(self) # zap cached _GeodesicLineExact attrs _B41, _C4a
|
|
291
|
+
self._nC4 = order
|
|
292
|
+
|
|
293
|
+
@deprecated_Property
|
|
294
|
+
def C4Order(self):
|
|
295
|
+
'''DEPRECATED, use property C{C4order}.
|
|
296
|
+
'''
|
|
297
|
+
return self.C4order
|
|
298
|
+
|
|
299
|
+
@C4Order.setter # PYCHOK .setter!
|
|
300
|
+
def C4Order(self, order):
|
|
301
|
+
'''DEPRECATED, use property C{C4order}.
|
|
302
|
+
'''
|
|
303
|
+
_xnC4(C4Order=order)
|
|
304
|
+
self.C4order = order
|
|
305
|
+
|
|
306
|
+
@Property_RO
|
|
307
|
+
def _C4x(self):
|
|
308
|
+
'''Get this ellipsoid's C{C4} coefficients, I{cached} tuple.
|
|
309
|
+
|
|
310
|
+
@see: Property L{C4order}.
|
|
311
|
+
'''
|
|
312
|
+
# see C4coeff() in GeographicLib.src.GeodesicExactC4.cpp
|
|
313
|
+
def _C4(nC4):
|
|
314
|
+
i, n, cs = 0, self.n, _C4coeffs(nC4)
|
|
315
|
+
_p = _polynomial
|
|
316
|
+
for r in range(nC4 + 1, 1, -1):
|
|
317
|
+
for j in range(1, r):
|
|
318
|
+
j = j + i # (j - i - 1) order of polynomial
|
|
319
|
+
yield _p(n, cs, i, j) / cs[j]
|
|
320
|
+
i = j + 1
|
|
321
|
+
# assert i == (nC4 * (nC4 + 1) * (nC4 + 5)) // 6
|
|
322
|
+
|
|
323
|
+
return tuple(_C4(self._nC4)) # 3rd flattening
|
|
324
|
+
|
|
325
|
+
@property_RO
|
|
326
|
+
def datum(self):
|
|
327
|
+
'''Get the datum (C{Datum}).
|
|
328
|
+
'''
|
|
329
|
+
return self._datum
|
|
330
|
+
|
|
331
|
+
def Direct(self, lat1, lon1, azi1, s12=0, outmask=Caps.STANDARD):
|
|
332
|
+
'''Solve the I{Direct} geodesic problem
|
|
333
|
+
|
|
334
|
+
@arg lat1: Latitude of the first point (C{degrees}).
|
|
335
|
+
@arg lon1: Longitude of the first point (C{degrees}).
|
|
336
|
+
@arg azi1: Azimuth at the first point (compass C{degrees}).
|
|
337
|
+
@arg s12: Distance between the points (C{meter}), can be negative.
|
|
338
|
+
@kwarg outmask: Bit-or'ed combination of L{Caps} values specifying
|
|
339
|
+
the quantities to be returned.
|
|
340
|
+
|
|
341
|
+
@return: A L{GDict} with up to 12 items C{lat1, lon1, azi1, lat2,
|
|
342
|
+
lon2, azi2, m12, a12, s12, M12, M21, S12} with C{lat1},
|
|
343
|
+
C{lon1}, C{azi1} and distance C{s12} always included.
|
|
344
|
+
|
|
345
|
+
@see: C++ U{GeodesicExact.Direct
|
|
346
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>}
|
|
347
|
+
and Python U{Geodesic.Direct<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
|
|
348
|
+
'''
|
|
349
|
+
return self._GDictDirect(lat1, lon1, azi1, False, s12, outmask)
|
|
350
|
+
|
|
351
|
+
def Direct3(self, lat1, lon1, azi1, s12): # PYCHOK outmask
|
|
352
|
+
'''Return the destination lat, lon and reverse azimuth
|
|
353
|
+
(final bearing) in C{degrees}.
|
|
354
|
+
|
|
355
|
+
@return: L{Destination3Tuple}C{(lat, lon, final)}.
|
|
356
|
+
'''
|
|
357
|
+
r = self._GDictDirect(lat1, lon1, azi1, False, s12, Caps._AZIMUTH_LATITUDE_LONGITUDE)
|
|
358
|
+
return Destination3Tuple(r.lat2, r.lon2, r.azi2) # no iteration
|
|
359
|
+
|
|
360
|
+
def _DirectLine(self, ll1, azi12, s12=0, **caps_name):
|
|
361
|
+
'''(INTERNAL) Short-cut version.
|
|
362
|
+
'''
|
|
363
|
+
return self.DirectLine(ll1.lat, ll1.lon, azi12, s12, **caps_name)
|
|
364
|
+
|
|
365
|
+
def DirectLine(self, lat1, lon1, azi1, s12, caps=Caps.STANDARD, name=NN):
|
|
366
|
+
'''Define a L{GeodesicLineExact} in terms of the I{direct} geodesic problem and as distance.
|
|
367
|
+
|
|
368
|
+
@arg lat1: Latitude of the first point (C{degrees}).
|
|
369
|
+
@arg lon1: Longitude of the first point (C{degrees}).
|
|
370
|
+
@arg azi1: Azimuth at the first point (compass C{degrees}).
|
|
371
|
+
@arg s12: Distance between the points (C{meter}), can be negative.
|
|
372
|
+
@kwarg caps: Bit-or'ed combination of L{Caps} values specifying
|
|
373
|
+
the capabilities the L{GeodesicLineExact} instance
|
|
374
|
+
should possess, i.e., which quantities can be
|
|
375
|
+
returned by calls to L{GeodesicLineExact.Position}.
|
|
376
|
+
|
|
377
|
+
@return: A L{GeodesicLineExact} instance.
|
|
378
|
+
|
|
379
|
+
@note: The third point of the L{GeodesicLineExact} is set to correspond
|
|
380
|
+
to the second point of the I{Inverse} geodesic problem.
|
|
381
|
+
|
|
382
|
+
@note: Latitude B{C{lat1}} should in the range C{[-90, +90]}.
|
|
383
|
+
|
|
384
|
+
@see: C++ U{GeodesicExact.DirectLine
|
|
385
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
|
|
386
|
+
Python U{Geodesic.DirectLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
|
|
387
|
+
'''
|
|
388
|
+
return self._GenDirectLine(lat1, lon1, azi1, False, s12, caps, name=name)
|
|
389
|
+
|
|
390
|
+
def _dn(self, sbet, cbet): # in gxline._GeodesicLineExact.__init__
|
|
391
|
+
'''(INTERNAL) Helper.
|
|
392
|
+
'''
|
|
393
|
+
if self.f < 0: # PYCHOK no cover
|
|
394
|
+
dn = sqrt(_1_0 - cbet**2 * self.e2) / self.f1
|
|
395
|
+
else:
|
|
396
|
+
dn = sqrt(_1_0 + sbet**2 * self.ep2)
|
|
397
|
+
return dn
|
|
398
|
+
|
|
399
|
+
@Property_RO
|
|
400
|
+
def e2(self):
|
|
401
|
+
'''Get the ellipsoid's I{(1st) eccentricity squared} (C{float}), M{f * (2 - f)}.
|
|
402
|
+
'''
|
|
403
|
+
return self.ellipsoid.e2
|
|
404
|
+
|
|
405
|
+
@Property_RO
|
|
406
|
+
def _e2a2(self):
|
|
407
|
+
'''(INTERNAL) Cache M{E.e2 * E.a2}.
|
|
408
|
+
'''
|
|
409
|
+
return self.e2 * self.ellipsoid.a2
|
|
410
|
+
|
|
411
|
+
@Property_RO
|
|
412
|
+
def _e2_f1(self):
|
|
413
|
+
'''(INTERNAL) Cache M{E.e2 * E.f1}.
|
|
414
|
+
'''
|
|
415
|
+
return self.e2 / self.f1
|
|
416
|
+
|
|
417
|
+
@Property_RO
|
|
418
|
+
def _eF(self):
|
|
419
|
+
'''(INTERNAL) Get the elliptic function, aka C{.E}.
|
|
420
|
+
'''
|
|
421
|
+
return _MODS.elliptic.Elliptic(k2=-self.ep2)
|
|
422
|
+
|
|
423
|
+
def _eF_reset_cHe2_f1(self, x, y):
|
|
424
|
+
'''(INTERNAL) Reset elliptic function and return M{cH * e2 / f1 * ...}.
|
|
425
|
+
'''
|
|
426
|
+
self._eF_reset_k2(x)
|
|
427
|
+
return y * self._eF.cH * self._e2_f1
|
|
428
|
+
|
|
429
|
+
def _eF_reset_k2(self, x):
|
|
430
|
+
'''(INTERNAL) Reset elliptic function and return C{k2}.
|
|
431
|
+
'''
|
|
432
|
+
ep2 = self.ep2
|
|
433
|
+
k2 = x**2 * ep2 # see .gxline._GeodesicLineExact._eF
|
|
434
|
+
self._eF.reset(k2=-k2, alpha2=-ep2) # kp2, alphap2 defaults
|
|
435
|
+
_update_glXs(self) # zap cached/memoized _GeodesicLineExact attrs
|
|
436
|
+
return k2
|
|
437
|
+
|
|
438
|
+
@Property_RO
|
|
439
|
+
def ellipsoid(self):
|
|
440
|
+
'''Get the ellipsoid (C{Ellipsoid}).
|
|
441
|
+
'''
|
|
442
|
+
return self.datum.ellipsoid
|
|
443
|
+
|
|
444
|
+
@Property_RO
|
|
445
|
+
def ep2(self):
|
|
446
|
+
'''Get the ellipsoid's I{2nd eccentricity squared} (C{float}), M{e2 / (1 - e2)}.
|
|
447
|
+
'''
|
|
448
|
+
return self.ellipsoid.e22 # == self.e2 / self.f1**2
|
|
449
|
+
|
|
450
|
+
e22 = ep2 # for ellipsoid compatibility
|
|
451
|
+
|
|
452
|
+
@Property_RO
|
|
453
|
+
def _eTOL2(self):
|
|
454
|
+
'''(INTERNAL) The si12 threshold for "really short".
|
|
455
|
+
'''
|
|
456
|
+
return _eTOL2(self.f)
|
|
457
|
+
|
|
458
|
+
@Property_RO
|
|
459
|
+
def flattening(self):
|
|
460
|
+
'''Get the C{ellipsoid}'s I{flattening} (C{scalar}), M{(a - b) / a}, C{0} for spherical, negative for prolate.
|
|
461
|
+
'''
|
|
462
|
+
return self.ellipsoid.f
|
|
463
|
+
|
|
464
|
+
f = flattening
|
|
465
|
+
|
|
466
|
+
@Property_RO
|
|
467
|
+
def f1(self): # in .css.CassiniSoldner.reset
|
|
468
|
+
'''Get the C{ellipsoid}'s I{1 - flattening} (C{float}).
|
|
469
|
+
'''
|
|
470
|
+
return self.ellipsoid.f1
|
|
471
|
+
|
|
472
|
+
@Property_RO
|
|
473
|
+
def _f180(self):
|
|
474
|
+
'''(INTERNAL) Cached/memoized.
|
|
475
|
+
'''
|
|
476
|
+
return self.f * _180_0
|
|
477
|
+
|
|
478
|
+
def _GDictDirect(self, lat1, lon1, azi1, arcmode, s12_a12, outmask=Caps.STANDARD):
|
|
479
|
+
'''(INTERNAL) As C{_GenDirect}, but returning a L{GDict}.
|
|
480
|
+
|
|
481
|
+
@return: A L{GDict} ...
|
|
482
|
+
'''
|
|
483
|
+
C = outmask if arcmode else (outmask | Caps.DISTANCE_IN)
|
|
484
|
+
glX = self.Line(lat1, lon1, azi1, C | Caps.LINE_OFF)
|
|
485
|
+
return glX._GDictPosition(arcmode, s12_a12, outmask)
|
|
486
|
+
|
|
487
|
+
def _GDictInverse(self, lat1, lon1, lat2, lon2, outmask=Caps.STANDARD): # MCCABE 33, 41 vars
|
|
488
|
+
'''(INTERNAL) As C{_GenInverse}, but returning a L{GDict}.
|
|
489
|
+
|
|
490
|
+
@return: A L{GDict} ...
|
|
491
|
+
'''
|
|
492
|
+
Cs = Caps
|
|
493
|
+
if self._debug: # PYCHOK no cover
|
|
494
|
+
outmask |= Cs._DEBUG_INVERSE & self._debug
|
|
495
|
+
outmask &= Cs._OUT_MASK # incl. _SALPs_CALPs and _DEBUG_
|
|
496
|
+
# compute longitude difference carefully (with _diff182):
|
|
497
|
+
# result is in [-180, +180] but -180 is only for west-going
|
|
498
|
+
# geodesics, +180 is for east-going and meridional geodesics
|
|
499
|
+
lon12, lon12s = _diff182(lon1, lon2)
|
|
500
|
+
# see C{result} from geographiclib.geodesic.Inverse
|
|
501
|
+
if (outmask & Cs.LONG_UNROLL): # == (lon1 + lon12) + lon12s
|
|
502
|
+
r = GDict(lon1=lon1, lon2=fsumf_(lon1, lon12, lon12s))
|
|
503
|
+
else:
|
|
504
|
+
r = GDict(lon1=_norm180(lon1), lon2=_norm180(lon2))
|
|
505
|
+
if _K_2_0: # GeographicLib 2.0
|
|
506
|
+
# make longitude difference positive
|
|
507
|
+
lon12, lon_ = _unsigned2(lon12)
|
|
508
|
+
if lon_:
|
|
509
|
+
lon12s = -lon12s
|
|
510
|
+
lam12 = radians(lon12)
|
|
511
|
+
# calculate sincosd(_around(lon12 + correction))
|
|
512
|
+
slam12, clam12 = _sincos2de(lon12, lon12s)
|
|
513
|
+
# supplementary longitude difference
|
|
514
|
+
lon12s = fsumf_(_180_0, -lon12, -lon12s)
|
|
515
|
+
else: # GeographicLib 1.52
|
|
516
|
+
# make longitude difference positive and if very close
|
|
517
|
+
# to being on the same half-meridian, then make it so.
|
|
518
|
+
if lon12 < 0: # _signBit(lon12)
|
|
519
|
+
lon_, lon12 = True, -_around(lon12)
|
|
520
|
+
lon12s = _around(fsumf_(_180_0, -lon12, lon12s))
|
|
521
|
+
else:
|
|
522
|
+
lon_, lon12 = False, _around(lon12)
|
|
523
|
+
lon12s = _around(fsumf_(_180_0, -lon12, -lon12s))
|
|
524
|
+
lam12 = radians(lon12)
|
|
525
|
+
if lon12 > _90_0:
|
|
526
|
+
slam12, clam12 = _sincos2d(lon12s)
|
|
527
|
+
clam12 = -clam12
|
|
528
|
+
else:
|
|
529
|
+
slam12, clam12 = _sincos2(lam12)
|
|
530
|
+
# If really close to the equator, treat as on equator.
|
|
531
|
+
lat1 = _around(_fix90(lat1))
|
|
532
|
+
lat2 = _around(_fix90(lat2))
|
|
533
|
+
r.set_(lat1=lat1, lat2=lat2)
|
|
534
|
+
# Swap points so that point with higher (abs) latitude is
|
|
535
|
+
# point 1. If one latitude is a NAN, then it becomes lat1.
|
|
536
|
+
swap_ = fabs(lat1) < fabs(lat2) or isnan(lat2)
|
|
537
|
+
if swap_:
|
|
538
|
+
lat1, lat2 = lat2, lat1
|
|
539
|
+
lon_ = not lon_
|
|
540
|
+
if _signBit(lat1):
|
|
541
|
+
lat_ = False # note, False
|
|
542
|
+
else: # make lat1 <= -0
|
|
543
|
+
lat_ = True # note, True
|
|
544
|
+
lat1, lat2 = -lat1, -lat2
|
|
545
|
+
# Now 0 <= lon12 <= 180, -90 <= lat1 <= -0 and lat1 <= lat2 <= -lat1
|
|
546
|
+
# and lat_, lon_, swap_ register the transformation to bring the
|
|
547
|
+
# coordinates to this canonical form, where False means no change
|
|
548
|
+
# made. We make these transformations so that there are few cases
|
|
549
|
+
# to check, e.g., on verifying quadrants in atan2. In addition,
|
|
550
|
+
# this enforces some symmetries in the results returned.
|
|
551
|
+
|
|
552
|
+
# Initialize for the meridian. No longitude calculation is
|
|
553
|
+
# done in this case to let the parameter default to 0.
|
|
554
|
+
sbet1, cbet1 = self._sinf1cos2d(lat1)
|
|
555
|
+
sbet2, cbet2 = self._sinf1cos2d(lat2)
|
|
556
|
+
# If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure
|
|
557
|
+
# of the |bet1| - |bet2|. Alternatively (cbet1 >= -sbet1),
|
|
558
|
+
# abs(sbet2) + sbet1 is a better measure. This logic is used
|
|
559
|
+
# in assigning calp2 in _Lambda6. Sometimes these quantities
|
|
560
|
+
# vanish and in that case we force bet2 = +/- bet1 exactly. An
|
|
561
|
+
# example where is is necessary is the inverse problem
|
|
562
|
+
# 48.522876735459 0 -48.52287673545898293 179.599720456223079643
|
|
563
|
+
# which failed with Visual Studio 10 (Release and Debug)
|
|
564
|
+
if cbet1 < -sbet1:
|
|
565
|
+
if cbet2 == cbet1:
|
|
566
|
+
sbet2 = copysign(sbet1, sbet2)
|
|
567
|
+
elif fabs(sbet2) == -sbet1:
|
|
568
|
+
cbet2 = cbet1
|
|
569
|
+
|
|
570
|
+
p = _PDict(sbet1=sbet1, cbet1=cbet1, dn1=self._dn(sbet1, cbet1),
|
|
571
|
+
sbet2=sbet2, cbet2=cbet2, dn2=self._dn(sbet2, cbet2))
|
|
572
|
+
|
|
573
|
+
_meridian = _b = True # i.e. not meridian, not b
|
|
574
|
+
if lat1 == -90 or slam12 == 0:
|
|
575
|
+
# Endpoints are on a single full meridian,
|
|
576
|
+
# so the geodesic might lie on a meridian.
|
|
577
|
+
salp1, calp1 = slam12, clam12 # head to target lon
|
|
578
|
+
salp2, calp2 = _0_0, _1_0 # then head north
|
|
579
|
+
# tan(bet) = tan(sig) * cos(alp)
|
|
580
|
+
p.set_sigs(sbet1, calp1 * cbet1, sbet2, calp2 * cbet2)
|
|
581
|
+
# sig12 = sig2 - sig1
|
|
582
|
+
sig12 = _atan12(sbet1, p.csig1, sbet2, p.csig2, sineg0=True) # PYCHOK csig*
|
|
583
|
+
s12x, m12x, _, \
|
|
584
|
+
M12, M21 = self._Length5(sig12, outmask | Cs.REDUCEDLENGTH, p)
|
|
585
|
+
# Add the check for sig12 since zero length geodesics
|
|
586
|
+
# might yield m12 < 0. Test case was
|
|
587
|
+
# echo 20.001 0 20.001 0 | GeodSolve -i
|
|
588
|
+
# In fact, we will have sig12 > PI/2 for meridional
|
|
589
|
+
# geodesic which is not a shortest path.
|
|
590
|
+
if m12x >= 0 or sig12 < _1_0:
|
|
591
|
+
# Need at least 2 to handle 90 0 90 180
|
|
592
|
+
# Prevent negative s12 or m12 from geographiclib 1.52
|
|
593
|
+
if sig12 < _TINY3 or (sig12 < _TOL0 and (s12x < 0 or m12x < 0)):
|
|
594
|
+
sig12 = m12x = s12x = _0_0
|
|
595
|
+
else:
|
|
596
|
+
_b = False # apply .b to s12x, m12x
|
|
597
|
+
_meridian = False
|
|
598
|
+
C = 1
|
|
599
|
+
# else: # m12 < 0, prolate and too close to anti-podal
|
|
600
|
+
# _meridian = True
|
|
601
|
+
a12 = _0_0 # if _b else degrees(sig12)
|
|
602
|
+
|
|
603
|
+
if _meridian:
|
|
604
|
+
_b = sbet1 == 0 and (self.f <= 0 or lon12s >= self._f180) # and sbet2 == 0
|
|
605
|
+
if _b: # Geodesic runs along equator
|
|
606
|
+
calp1 = calp2 = _0_0
|
|
607
|
+
salp1 = salp2 = _1_0
|
|
608
|
+
sig12 = lam12 / self.f1 # == omg12
|
|
609
|
+
somg12, comg12 = _sincos2(sig12)
|
|
610
|
+
m12x = self.b * somg12
|
|
611
|
+
s12x = self.a * lam12
|
|
612
|
+
M12 = M21 = comg12
|
|
613
|
+
a12 = lon12 / self.f1
|
|
614
|
+
C = 2
|
|
615
|
+
else:
|
|
616
|
+
# Now point1 and point2 belong within a hemisphere bounded by a
|
|
617
|
+
# meridian and geodesic is neither meridional or equatorial.
|
|
618
|
+
p.set_(slam12=slam12, clam12=clam12)
|
|
619
|
+
# Figure a starting point for Newton's method
|
|
620
|
+
sig12, salp1, calp1, \
|
|
621
|
+
salp2, calp2, dnm = self._InverseStart6(lam12, p)
|
|
622
|
+
if sig12 is None: # use Newton's method
|
|
623
|
+
# pre-compute the constant _Lambda6 term, once
|
|
624
|
+
p.set_(bet12=None if cbet2 == cbet1 and fabs(sbet2) == -sbet1 else
|
|
625
|
+
(((cbet1 + cbet2) * (cbet2 - cbet1)) if cbet1 < -sbet1 else
|
|
626
|
+
((sbet1 + sbet2) * (sbet1 - sbet2))))
|
|
627
|
+
sig12, salp1, calp1, \
|
|
628
|
+
salp2, calp2, domg12 = self._Newton6(salp1, calp1, p)
|
|
629
|
+
s12x, m12x, _, M12, M21 = self._Length5(sig12, outmask, p)
|
|
630
|
+
if (outmask & Cs.AREA):
|
|
631
|
+
# omg12 = lam12 - domg12
|
|
632
|
+
s, c = _sincos2(domg12)
|
|
633
|
+
somg12, comg12 = _sincos12(s, c, slam12, clam12)
|
|
634
|
+
C = 3 # Newton
|
|
635
|
+
else: # from _InverseStart6: dnm, salp*, calp*
|
|
636
|
+
C = 4 # Short lines
|
|
637
|
+
s, c = _sincos2(sig12 / dnm)
|
|
638
|
+
m12x = dnm**2 * s
|
|
639
|
+
s12x = dnm * sig12
|
|
640
|
+
M12 = M21 = c
|
|
641
|
+
if (outmask & Cs.AREA):
|
|
642
|
+
somg12, comg12 = _sincos2(lam12 / (self.f1 * dnm))
|
|
643
|
+
|
|
644
|
+
else: # _meridian is False
|
|
645
|
+
somg12 = comg12 = NAN
|
|
646
|
+
|
|
647
|
+
r.set_(a12=a12 if _b else degrees(sig12)) # in [0, 180]
|
|
648
|
+
|
|
649
|
+
if (outmask & Cs.DISTANCE):
|
|
650
|
+
r.set_(s12=unsigned0(s12x if _b else (self.b * s12x)))
|
|
651
|
+
|
|
652
|
+
if (outmask & Cs.REDUCEDLENGTH):
|
|
653
|
+
r.set_(m12=unsigned0(m12x if _b else (self.b * m12x)))
|
|
654
|
+
|
|
655
|
+
if (outmask & Cs.GEODESICSCALE):
|
|
656
|
+
if swap_:
|
|
657
|
+
M12, M21 = M21, M12
|
|
658
|
+
r.set_(M12=unsigned0(M12),
|
|
659
|
+
M21=unsigned0(M21))
|
|
660
|
+
|
|
661
|
+
if (outmask & Cs.AREA):
|
|
662
|
+
S12 = self._InverseArea(_meridian, salp1, calp1,
|
|
663
|
+
salp2, calp2,
|
|
664
|
+
somg12, comg12, p)
|
|
665
|
+
if _xor(swap_, lat_, lon_):
|
|
666
|
+
S12 = -S12
|
|
667
|
+
r.set_(S12=unsigned0(S12))
|
|
668
|
+
|
|
669
|
+
if (outmask & (Cs.AZIMUTH | Cs._SALPs_CALPs)):
|
|
670
|
+
if swap_:
|
|
671
|
+
salp1, salp2 = salp2, salp1
|
|
672
|
+
calp1, calp2 = calp2, calp1
|
|
673
|
+
if _xor(swap_, lon_):
|
|
674
|
+
salp1, salp2 = -salp1, -salp2
|
|
675
|
+
if _xor(swap_, lat_):
|
|
676
|
+
calp1, calp2 = -calp1, -calp2
|
|
677
|
+
|
|
678
|
+
if (outmask & Cs.AZIMUTH):
|
|
679
|
+
r.set_(azi1=_atan2d(salp1, calp1),
|
|
680
|
+
azi2=_atan2d_reverse(salp2, calp2, reverse=outmask & Cs.REVERSE2))
|
|
681
|
+
if (outmask & Cs._SALPs_CALPs):
|
|
682
|
+
r.set_(salp1=salp1, calp1=calp1,
|
|
683
|
+
salp2=salp2, calp2=calp2)
|
|
684
|
+
|
|
685
|
+
if (outmask & Cs._DEBUG_INVERSE): # PYCHOK no cover
|
|
686
|
+
E, eF = self.ellipsoid, self._eF
|
|
687
|
+
p.set_(C=C, a=self.a, f=self.f, f1=self.f1,
|
|
688
|
+
e=E.e, e2=self.e2, ep2=self.ep2,
|
|
689
|
+
c2=E.c2, c2x=self.c2x,
|
|
690
|
+
eFcD=eF.cD, eFcE=eF.cE, eFcH=eF.cH,
|
|
691
|
+
eFk2=eF.k2, eFa2=eF.alpha2)
|
|
692
|
+
p.update(r) # r overrides p
|
|
693
|
+
r = p.toGDict()
|
|
694
|
+
return self._iter2tion(r, p)
|
|
695
|
+
|
|
696
|
+
def _GenDirect(self, lat1, lon1, azi1, arcmode, s12_a12, outmask=Caps.STANDARD):
|
|
697
|
+
'''(INTERNAL) The general I{Inverse} geodesic calculation.
|
|
698
|
+
|
|
699
|
+
@return: L{Direct9Tuple}C{(a12, lat2, lon2, azi2,
|
|
700
|
+
s12, m12, M12, M21, S12)}.
|
|
701
|
+
'''
|
|
702
|
+
r = self._GDictDirect(lat1, lon1, azi1, arcmode, s12_a12, outmask)
|
|
703
|
+
return r.toDirect9Tuple()
|
|
704
|
+
|
|
705
|
+
def _GenDirectLine(self, lat1, lon1, azi1, arcmode, s12_a12, caps, name=NN):
|
|
706
|
+
'''(INTERNAL) Helper for C{ArcDirectLine} and C{DirectLine}.
|
|
707
|
+
|
|
708
|
+
@return: A L{GeodesicLineExact} instance.
|
|
709
|
+
'''
|
|
710
|
+
azi1 = _norm180(azi1)
|
|
711
|
+
# guard against underflow in salp0. Also -0 is converted to +0.
|
|
712
|
+
s, c = _sincos2d(_around(azi1))
|
|
713
|
+
C = caps if arcmode else (caps | Caps.DISTANCE_IN)
|
|
714
|
+
return _GeodesicLineExact(self, lat1, lon1, azi1, C,
|
|
715
|
+
self._debug, s, c, name=name)._GenSet(arcmode, s12_a12)
|
|
716
|
+
|
|
717
|
+
def _GenInverse(self, lat1, lon1, lat2, lon2, outmask=Caps.STANDARD):
|
|
718
|
+
'''(INTERNAL) The general I{Inverse} geodesic calculation.
|
|
719
|
+
|
|
720
|
+
@return: L{Inverse10Tuple}C{(a12, s12, salp1, calp1, salp2, calp2,
|
|
721
|
+
m12, M12, M21, S12)}.
|
|
722
|
+
'''
|
|
723
|
+
r = self._GDictInverse(lat1, lon1, lat2, lon2, outmask | Caps._SALPs_CALPs)
|
|
724
|
+
return r.toInverse10Tuple()
|
|
725
|
+
|
|
726
|
+
def _Inverse(self, ll1, ll2, wrap, **outmask):
|
|
727
|
+
'''(INTERNAL) Short-cut version, see .base.ellipsoidalDI.intersecant2.
|
|
728
|
+
'''
|
|
729
|
+
if wrap:
|
|
730
|
+
ll2 = _unrollon(ll1, _Wrap.point(ll2))
|
|
731
|
+
return self.Inverse(ll1.lat, ll1.lon, ll2.lat, ll2.lon, **outmask)
|
|
732
|
+
|
|
733
|
+
def Inverse(self, lat1, lon1, lat2, lon2, outmask=Caps.STANDARD):
|
|
734
|
+
'''Perform the I{Inverse} geodesic calculation.
|
|
735
|
+
|
|
736
|
+
@arg lat1: Latitude of the first point (C{degrees}).
|
|
737
|
+
@arg lon1: Longitude of the first point (C{degrees}).
|
|
738
|
+
@arg lat2: Latitude of the second point (C{degrees}).
|
|
739
|
+
@arg lon2: Longitude of the second point (C{degrees}).
|
|
740
|
+
@kwarg outmask: Bit-or'ed combination of L{Caps} values specifying
|
|
741
|
+
the quantities to be returned.
|
|
742
|
+
|
|
743
|
+
@return: A L{GDict} with up to 12 items C{lat1, lon1, azi1, lat2,
|
|
744
|
+
lon2, azi2, m12, a12, s12, M12, M21, S12} with C{lat1},
|
|
745
|
+
C{lon1}, C{azi1} and distance C{s12} always included.
|
|
746
|
+
|
|
747
|
+
@note: The third point of the L{GeodesicLineExact} is set to correspond
|
|
748
|
+
to the second point of the I{Inverse} geodesic problem.
|
|
749
|
+
|
|
750
|
+
@note: Both B{C{lat1}} and B{C{lat2}} should in the range C{[-90, +90]}.
|
|
751
|
+
|
|
752
|
+
@see: C++ U{GeodesicExact.InverseLine
|
|
753
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
|
|
754
|
+
Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
|
|
755
|
+
'''
|
|
756
|
+
return self._GDictInverse(lat1, lon1, lat2, lon2, outmask)
|
|
757
|
+
|
|
758
|
+
def Inverse1(self, lat1, lon1, lat2, lon2, wrap=False):
|
|
759
|
+
'''Return the non-negative, I{angular} distance in C{degrees}.
|
|
760
|
+
|
|
761
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
762
|
+
B{C{lat2}} and B{C{lon2}} (C{bool}).
|
|
763
|
+
'''
|
|
764
|
+
# see .FrechetKarney.distance, .HausdorffKarney._distance
|
|
765
|
+
# and .HeightIDWkarney._distances
|
|
766
|
+
if wrap:
|
|
767
|
+
_, lat2, lon2 = _Wrap.latlon3(lat1, lat2, lon2, True) # _Geodesic.LONG_UNROLL
|
|
768
|
+
return fabs(self._GDictInverse(lat1, lon1, lat2, lon2, Caps._ANGLE_ONLY).a12)
|
|
769
|
+
|
|
770
|
+
def Inverse3(self, lat1, lon1, lat2, lon2): # PYCHOK outmask
|
|
771
|
+
'''Return the distance in C{meter} and the forward and
|
|
772
|
+
reverse azimuths (initial and final bearing) in C{degrees}.
|
|
773
|
+
|
|
774
|
+
@return: L{Distance3Tuple}C{(distance, initial, final)}.
|
|
775
|
+
'''
|
|
776
|
+
r = self._GDictInverse(lat1, lon1, lat2, lon2, Caps.AZIMUTH_DISTANCE)
|
|
777
|
+
return Distance3Tuple(r.s12, wrap360(r.azi1), wrap360(r.azi2),
|
|
778
|
+
iteration=r.iteration)
|
|
779
|
+
|
|
780
|
+
def _InverseLine(self, ll1, ll2, wrap, **caps_name):
|
|
781
|
+
'''(INTERNAL) Short-cut version.
|
|
782
|
+
'''
|
|
783
|
+
if wrap:
|
|
784
|
+
ll2 = _unrollon(ll1, _Wrap.point(ll2))
|
|
785
|
+
return self.InverseLine(ll1.lat, ll1.lon, ll2.lat, ll2.lon, **caps_name)
|
|
786
|
+
|
|
787
|
+
def InverseLine(self, lat1, lon1, lat2, lon2, caps=Caps.STANDARD, name=NN):
|
|
788
|
+
'''Define a L{GeodesicLineExact} in terms of the I{Inverse} geodesic problem.
|
|
789
|
+
|
|
790
|
+
@arg lat1: Latitude of the first point (C{degrees}).
|
|
791
|
+
@arg lon1: Longitude of the first point (C{degrees}).
|
|
792
|
+
@arg lat2: Latitude of the second point (C{degrees}).
|
|
793
|
+
@arg lon2: Longitude of the second point (C{degrees}).
|
|
794
|
+
@kwarg caps: Bit-or'ed combination of L{Caps} values specifying
|
|
795
|
+
the capabilities the L{GeodesicLineExact} instance
|
|
796
|
+
should possess, i.e., which quantities can be
|
|
797
|
+
returned by calls to L{GeodesicLineExact.Position}
|
|
798
|
+
and L{GeodesicLineExact.ArcPosition}.
|
|
799
|
+
|
|
800
|
+
@return: A L{GeodesicLineExact} instance.
|
|
801
|
+
|
|
802
|
+
@note: The third point of the L{GeodesicLineExact} is set to correspond
|
|
803
|
+
to the second point of the I{Inverse} geodesic problem.
|
|
804
|
+
|
|
805
|
+
@note: Both B{C{lat1}} and B{C{lat2}} should in the range C{[-90, +90]}.
|
|
806
|
+
|
|
807
|
+
@see: C++ U{GeodesicExact.InverseLine
|
|
808
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and
|
|
809
|
+
Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
|
|
810
|
+
'''
|
|
811
|
+
Cs = Caps
|
|
812
|
+
r = self._GDictInverse(lat1, lon1, lat2, lon2, Cs._SALPs_CALPs) # No need for AZIMUTH
|
|
813
|
+
C = (caps | Cs.DISTANCE) if (caps & (Cs.DISTANCE_IN & Cs._OUT_MASK)) else caps
|
|
814
|
+
azi1 = _atan2d(r.salp1, r.calp1)
|
|
815
|
+
return _GeodesicLineExact(self, lat1, lon1, azi1, C, # ensure a12 is distance
|
|
816
|
+
self._debug, r.salp1, r.calp1, name=name)._GenSet(True, r.a12)
|
|
817
|
+
|
|
818
|
+
def _InverseArea(self, _meridian, salp1, calp1, # PYCHOK 9 args
|
|
819
|
+
salp2, calp2,
|
|
820
|
+
somg12, comg12, p):
|
|
821
|
+
'''(INTERNAL) Split off from C{_GDictInverse} to reduce complexity/length.
|
|
822
|
+
|
|
823
|
+
@return: Area C{S12}.
|
|
824
|
+
'''
|
|
825
|
+
# from _Lambda6: sin(alp1) * cos(bet1) = sin(alp0), calp0 > 0
|
|
826
|
+
salp0, calp0 = _sin1cos2(salp1, calp1, p.sbet1, p.cbet1)
|
|
827
|
+
A4 = calp0 * salp0
|
|
828
|
+
if A4:
|
|
829
|
+
# from _Lambda6: tan(bet) = tan(sig) * cos(alp)
|
|
830
|
+
k2 = calp0**2 * self.ep2
|
|
831
|
+
C4a = self._C4f_k2(k2)
|
|
832
|
+
B41 = _cosSeries(C4a, *_norm2(p.sbet1, calp1 * p.cbet1))
|
|
833
|
+
B42 = _cosSeries(C4a, *_norm2(p.sbet2, calp2 * p.cbet2))
|
|
834
|
+
# multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0)
|
|
835
|
+
A4 *= self._e2a2
|
|
836
|
+
S12 = A4 * (B42 - B41)
|
|
837
|
+
else: # avoid problems with indeterminate sig1, sig2 on equator
|
|
838
|
+
A4 = B41 = B42 = k2 = S12 = _0_0
|
|
839
|
+
|
|
840
|
+
if (_meridian and # omg12 < 3/4 * PI
|
|
841
|
+
comg12 > -_SQRT2_2 and # lon diff not too big
|
|
842
|
+
(p.sbet2 - p.sbet1) < _1_75): # lat diff not too big
|
|
843
|
+
# use tan(Gamma/2) = tan(omg12/2) *
|
|
844
|
+
# (tan(bet1/2) + tan(bet2/2)) /
|
|
845
|
+
# (tan(bet1/2) * tan(bet2/2) + 1))
|
|
846
|
+
# with tan(x/2) = sin(x) / (1 + cos(x))
|
|
847
|
+
dbet1 = p.cbet1 + _1_0
|
|
848
|
+
dbet2 = p.cbet2 + _1_0
|
|
849
|
+
domg12 = comg12 + _1_0
|
|
850
|
+
salp12 = (p.sbet1 * dbet2 + dbet1 * p.sbet2) * somg12
|
|
851
|
+
calp12 = (p.sbet1 * p.sbet2 + dbet1 * dbet2) * domg12
|
|
852
|
+
alp12 = atan2(salp12, calp12) * _2_0
|
|
853
|
+
else:
|
|
854
|
+
# alp12 = alp2 - alp1, used in atan2, no need to normalize
|
|
855
|
+
salp12, calp12 = _sincos12(salp1, calp1, salp2, calp2)
|
|
856
|
+
# The right thing appears to happen if alp1 = +/-180 and
|
|
857
|
+
# alp2 = 0, viz salp12 = -0 and alp12 = -180. However,
|
|
858
|
+
# this depends on the sign being attached to 0 correctly.
|
|
859
|
+
# Following ensures the correct behavior.
|
|
860
|
+
if salp12 == 0 and calp12 < 0:
|
|
861
|
+
alp12 = _copysign(PI, calp1)
|
|
862
|
+
else:
|
|
863
|
+
alp12 = atan2(salp12, calp12)
|
|
864
|
+
|
|
865
|
+
p.set_(alp12=alp12, A4=A4, B41=B41, B42=B42, k2=k2)
|
|
866
|
+
return S12 + self.c2x * alp12
|
|
867
|
+
|
|
868
|
+
def _InverseStart6(self, lam12, p):
|
|
869
|
+
'''(INTERNAL) Return a starting point for Newton's method in
|
|
870
|
+
C{salp1} and C{calp1} indicated by C{sig12=None}. If
|
|
871
|
+
Newton's method doesn't need to be used, return also
|
|
872
|
+
C{salp2}, C{calp2}, C{dnm} and C{sig12} non-C{None}.
|
|
873
|
+
|
|
874
|
+
@return: 6-Tuple C{(sig12, salp1, calp1, salp2, calp2, dnm)}
|
|
875
|
+
and C{p.set_sigs} updated for Newton, C{sig12=None}.
|
|
876
|
+
'''
|
|
877
|
+
sig12 = None # use Newton
|
|
878
|
+
salp1 = calp1 = salp2 = calp2 = dnm = NAN
|
|
879
|
+
|
|
880
|
+
# bet12 = bet2 - bet1 in [0, PI)
|
|
881
|
+
sbet12, cbet12 = _sincos12(p.sbet1, p.cbet1, p.sbet2, p.cbet2)
|
|
882
|
+
shortline = cbet12 >= 0 and sbet12 < _0_5 and (p.cbet2 * lam12) < _0_5
|
|
883
|
+
if shortline:
|
|
884
|
+
# sin((bet1 + bet2)/2)^2 = (sbet1 + sbet2)^2 / (
|
|
885
|
+
# (cbet1 + cbet2)^2 + (sbet1 + sbet2)^2)
|
|
886
|
+
t = (p.sbet1 + p.sbet2)**2
|
|
887
|
+
s = t / ((p.cbet1 + p.cbet2)**2 + t)
|
|
888
|
+
dnm = sqrt(_1_0 + self.ep2 * s)
|
|
889
|
+
somg12, comg12 = _sincos2(lam12 / (self.f1 * dnm))
|
|
890
|
+
else:
|
|
891
|
+
somg12, comg12 = p.slam12, p.clam12
|
|
892
|
+
|
|
893
|
+
# bet12a = bet2 + bet1 in (-PI, 0], note -sbet1
|
|
894
|
+
sbet12a, cbet12a = _sincos12(-p.sbet1, p.cbet1, p.sbet2, p.cbet2)
|
|
895
|
+
|
|
896
|
+
c = fabs(comg12) + _1_0 # == (1 - comg12) if comg12 < 0
|
|
897
|
+
s = somg12**2 / c
|
|
898
|
+
t = p.sbet1 * p.cbet2 * s
|
|
899
|
+
salp1 = p.cbet2 * somg12
|
|
900
|
+
calp1 = (sbet12a - t) if comg12 < 0 else (sbet12 + t)
|
|
901
|
+
|
|
902
|
+
ssig12 = _hypot(salp1, calp1)
|
|
903
|
+
csig12 = p.sbet1 * p.sbet2 + p.cbet1 * p.cbet2 * comg12
|
|
904
|
+
|
|
905
|
+
if shortline and ssig12 < self._eTOL2: # really short lines
|
|
906
|
+
t = c if comg12 < 0 else s
|
|
907
|
+
salp2, calp2 = _norm2(somg12 * p.cbet1,
|
|
908
|
+
sbet12 - p.cbet1 * p.sbet2 * t)
|
|
909
|
+
sig12 = atan2(ssig12, csig12) # do not use Newton
|
|
910
|
+
|
|
911
|
+
elif (self._n_0_1 or # Skip astroid calc if too eccentric
|
|
912
|
+
csig12 >= 0 or ssig12 >= (p.cbet1**2 * self._n6PI)):
|
|
913
|
+
pass # nothing to do, 0th order spherical approximation OK
|
|
914
|
+
|
|
915
|
+
else:
|
|
916
|
+
# Scale lam12 and bet2 to x, y coordinate system where antipodal
|
|
917
|
+
# point is at origin and singular point is at y = 0, x = -1
|
|
918
|
+
lam12x = atan2(-p.slam12, -p.clam12) # lam12 - PI
|
|
919
|
+
f = self.f
|
|
920
|
+
if f < 0: # PYCHOK no cover
|
|
921
|
+
# ssig1=sbet1, csig1=-cbet1, ssig2=sbet2, csig2=cbet2
|
|
922
|
+
p.set_sigs(p.sbet1, -p.cbet1, p.sbet2, p.cbet2)
|
|
923
|
+
# if lon12 = 180, this repeats a calculation made in Inverse
|
|
924
|
+
_, m12b, m0, _, _ = self._Length5(atan2(sbet12a, cbet12a) + PI,
|
|
925
|
+
Caps.REDUCEDLENGTH, p)
|
|
926
|
+
t = p.cbet1 * PI # x = dlat, y = dlon
|
|
927
|
+
x = m12b / (t * p.cbet2 * m0) - _1_0
|
|
928
|
+
sca = (sbet12a / (x * p.cbet1)) if x < -_0_01 else (-f * t)
|
|
929
|
+
y = lam12x / sca
|
|
930
|
+
else: # f >= 0, however f == 0 does not get here
|
|
931
|
+
sca = self._eF_reset_cHe2_f1(p.sbet1, p.cbet1 * _2_0)
|
|
932
|
+
x = lam12x / sca # dlon
|
|
933
|
+
y = sbet12a / (sca * p.cbet1) # dlat
|
|
934
|
+
|
|
935
|
+
if y > _TOL1 and x > -_THR1: # strip near cut
|
|
936
|
+
if f < 0: # PYCHOK no cover
|
|
937
|
+
calp1 = max( _0_0, x) if x > _TOL1 else max(_N_1_0, x)
|
|
938
|
+
salp1 = sqrt(_1_0 - calp1**2)
|
|
939
|
+
else:
|
|
940
|
+
salp1 = min( _1_0, -x)
|
|
941
|
+
calp1 = -sqrt(_1_0 - salp1**2)
|
|
942
|
+
else:
|
|
943
|
+
# Estimate alp1, by solving the astroid problem.
|
|
944
|
+
#
|
|
945
|
+
# Could estimate alpha1 = theta + PI/2, directly, i.e.,
|
|
946
|
+
# calp1 = y/k; salp1 = -x/(1+k); for _f >= 0
|
|
947
|
+
# calp1 = x/(1+k); salp1 = -y/k; for _f < 0 (need to check)
|
|
948
|
+
#
|
|
949
|
+
# However, it's better to estimate omg12 from astroid and use
|
|
950
|
+
# spherical formula to compute alp1. This reduces the mean
|
|
951
|
+
# number of Newton iterations for astroid cases from 2.24
|
|
952
|
+
# (min 0, max 6) to 2.12 (min 0, max 5). The changes in the
|
|
953
|
+
# number of iterations are as follows:
|
|
954
|
+
#
|
|
955
|
+
# change percent
|
|
956
|
+
# 1 5
|
|
957
|
+
# 0 78
|
|
958
|
+
# -1 16
|
|
959
|
+
# -2 0.6
|
|
960
|
+
# -3 0.04
|
|
961
|
+
# -4 0.002
|
|
962
|
+
#
|
|
963
|
+
# The histogram of iterations is (m = number of iterations
|
|
964
|
+
# estimating alp1 directly, n = number of iterations
|
|
965
|
+
# estimating via omg12, total number of trials = 148605):
|
|
966
|
+
#
|
|
967
|
+
# iter m n
|
|
968
|
+
# 0 148 186
|
|
969
|
+
# 1 13046 13845
|
|
970
|
+
# 2 93315 102225
|
|
971
|
+
# 3 36189 32341
|
|
972
|
+
# 4 5396 7
|
|
973
|
+
# 5 455 1
|
|
974
|
+
# 6 56 0
|
|
975
|
+
#
|
|
976
|
+
# omg12 is near PI, estimate work with omg12a = PI - omg12
|
|
977
|
+
k = _Astroid(x, y)
|
|
978
|
+
sca *= (y * (k + _1_0) / k) if f < 0 else \
|
|
979
|
+
(x * k / (k + _1_0))
|
|
980
|
+
s, c = _sincos2(-sca) # omg12a
|
|
981
|
+
# update spherical estimate of alp1 using omg12 instead of lam12
|
|
982
|
+
salp1 = p.cbet2 * s
|
|
983
|
+
calp1 = sbet12a - s * salp1 * p.sbet1 / (c + _1_0) # c = -c
|
|
984
|
+
|
|
985
|
+
# sanity check on starting guess. Backwards check allows NaN through.
|
|
986
|
+
salp1, calp1 = _norm2(salp1, calp1) if salp1 > 0 else (_1_0, _0_0)
|
|
987
|
+
|
|
988
|
+
return sig12, salp1, calp1, salp2, calp2, dnm
|
|
989
|
+
|
|
990
|
+
def _Lambda6(self, salp1, calp1, diffp, p):
|
|
991
|
+
'''(INTERNAL) Helper.
|
|
992
|
+
|
|
993
|
+
@return: 6-Tuple C{(lam12, sig12, salp2, calp2, domg12,
|
|
994
|
+
dlam12} and C{p.set_sigs} updated.
|
|
995
|
+
'''
|
|
996
|
+
eF = self._eF
|
|
997
|
+
f1 = self.f1
|
|
998
|
+
|
|
999
|
+
if p.sbet1 == calp1 == 0: # PYCHOK no cover
|
|
1000
|
+
# Break degeneracy of equatorial line
|
|
1001
|
+
calp1 = -_TINY
|
|
1002
|
+
|
|
1003
|
+
# sin(alp1) * cos(bet1) = sin(alp0), # calp0 > 0
|
|
1004
|
+
salp0, calp0 = _sin1cos2(salp1, calp1, p.sbet1, p.cbet1)
|
|
1005
|
+
# tan(bet1) = tan(sig1) * cos(alp1)
|
|
1006
|
+
# tan(omg1) = sin(alp0) * tan(sig1)
|
|
1007
|
+
# = sin(bet1) * tan(alp1)
|
|
1008
|
+
somg1 = salp0 * p.sbet1
|
|
1009
|
+
comg1 = calp1 * p.cbet1
|
|
1010
|
+
ssig1, csig1 = _norm2(p.sbet1, comg1)
|
|
1011
|
+
# Without normalization we have schi1 = somg1
|
|
1012
|
+
cchi1 = f1 * p.dn1 * comg1
|
|
1013
|
+
|
|
1014
|
+
# Enforce symmetries in the case abs(bet2) = -bet1.
|
|
1015
|
+
# Need to be careful about this case, since this can
|
|
1016
|
+
# yield singularities in the Newton iteration.
|
|
1017
|
+
# sin(alp2) * cos(bet2) = sin(alp0)
|
|
1018
|
+
salp2 = (salp0 / p.cbet2) if p.cbet2 != p.cbet1 else salp1
|
|
1019
|
+
# calp2 = sqrt(1 - sq(salp2))
|
|
1020
|
+
# = sqrt(sq(calp0) - sq(sbet2)) / cbet2
|
|
1021
|
+
# and subst for calp0 and rearrange to give (choose
|
|
1022
|
+
# positive sqrt to give alp2 in [0, PI/2]).
|
|
1023
|
+
calp2 = fabs(calp1) if p.bet12 is None else (
|
|
1024
|
+
sqrt((calp1 * p.cbet1)**2 + p.bet12) / p.cbet2)
|
|
1025
|
+
# tan(bet2) = tan(sig2) * cos(alp2)
|
|
1026
|
+
# tan(omg2) = sin(alp0) * tan(sig2).
|
|
1027
|
+
somg2 = salp0 * p.sbet2
|
|
1028
|
+
comg2 = calp2 * p.cbet2
|
|
1029
|
+
ssig2, csig2 = _norm2(p.sbet2, comg2)
|
|
1030
|
+
# without normalization we have schi2 = somg2
|
|
1031
|
+
cchi2 = f1 * p.dn2 * comg2
|
|
1032
|
+
|
|
1033
|
+
# omg12 = omg2 - omg1, limit to [0, PI]
|
|
1034
|
+
somg12, comg12 = _sincos12(somg1, comg1, somg2, comg2, sineg0=True)
|
|
1035
|
+
# chi12 = chi2 - chi1, limit to [0, PI]
|
|
1036
|
+
schi12, cchi12 = _sincos12(somg1, cchi1, somg2, cchi2, sineg0=True)
|
|
1037
|
+
|
|
1038
|
+
p.set_sigs(ssig1, csig1, ssig2, csig2)
|
|
1039
|
+
# sig12 = sig2 - sig1, limit to [0, PI]
|
|
1040
|
+
sig12 = _atan12(ssig1, csig1, ssig2, csig2, sineg0=True)
|
|
1041
|
+
|
|
1042
|
+
eta12 = self._eF_reset_cHe2_f1(calp0, salp0) * _2__PI # then ...
|
|
1043
|
+
eta12 *= fsum1f_(eF.deltaH(*p.sncndn2),
|
|
1044
|
+
-eF.deltaH(*p.sncndn1), sig12)
|
|
1045
|
+
# eta = chi12 - lam12
|
|
1046
|
+
lam12 = _atan12(p.slam12, p.clam12, schi12, cchi12) - eta12
|
|
1047
|
+
# domg12 = chi12 - omg12 - deta12
|
|
1048
|
+
domg12 = _atan12( somg12, comg12, schi12, cchi12) - eta12
|
|
1049
|
+
|
|
1050
|
+
dlam12 = NAN # dv > 0 in ._Newton6
|
|
1051
|
+
if diffp:
|
|
1052
|
+
d = calp2 * p.cbet2
|
|
1053
|
+
if d:
|
|
1054
|
+
_, dlam12, _, _, _ = self._Length5(sig12, Caps.REDUCEDLENGTH, p)
|
|
1055
|
+
dlam12 *= f1 / d
|
|
1056
|
+
elif p.sbet1:
|
|
1057
|
+
dlam12 = -f1 * p.dn1 * _2_0 / p.sbet1
|
|
1058
|
+
|
|
1059
|
+
# p.set_(deta12=-eta12, lam12=lam12)
|
|
1060
|
+
return lam12, sig12, salp2, calp2, domg12, dlam12
|
|
1061
|
+
|
|
1062
|
+
def _Length5(self, sig12, outmask, p):
|
|
1063
|
+
'''(INTERNAL) Return M{m12b = (reduced length) / self.b} and
|
|
1064
|
+
calculate M{s12b = distance / self.b} and M{m0}, the
|
|
1065
|
+
coefficient of secular term in expression for reduced
|
|
1066
|
+
length and the geodesic scales C{M12} and C{M21}.
|
|
1067
|
+
|
|
1068
|
+
@return: 5-Tuple C{(s12b, m12b, m0, M12, M21)}.
|
|
1069
|
+
'''
|
|
1070
|
+
s12b = m12b = m0 = M12 = M21 = NAN
|
|
1071
|
+
|
|
1072
|
+
Cs = Caps
|
|
1073
|
+
eF = self._eF
|
|
1074
|
+
|
|
1075
|
+
# outmask &= Cs._OUT_MASK
|
|
1076
|
+
if (outmask & Cs.DISTANCE):
|
|
1077
|
+
# Missing a factor of self.b
|
|
1078
|
+
s12b = eF.cE * _2__PI * fsum1f_(eF.deltaE(*p.sncndn2),
|
|
1079
|
+
-eF.deltaE(*p.sncndn1), sig12)
|
|
1080
|
+
|
|
1081
|
+
if (outmask & Cs._REDUCEDLENGTH_GEODESICSCALE):
|
|
1082
|
+
m0x = -eF.k2 * eF.cD * _2__PI
|
|
1083
|
+
J12 = -m0x * fsum1f_(eF.deltaD(*p.sncndn2),
|
|
1084
|
+
-eF.deltaD(*p.sncndn1), sig12)
|
|
1085
|
+
if (outmask & Cs.REDUCEDLENGTH):
|
|
1086
|
+
m0 = m0x
|
|
1087
|
+
# Missing a factor of self.b. Add parens around
|
|
1088
|
+
# (csig1 * ssig2) and (ssig1 * csig2) to ensure
|
|
1089
|
+
# accurate cancellation for coincident points.
|
|
1090
|
+
m12b = fsum1f_(p.dn2 * (p.csig1 * p.ssig2),
|
|
1091
|
+
-p.dn1 * (p.ssig1 * p.csig2),
|
|
1092
|
+
J12 * (p.csig1 * p.csig2))
|
|
1093
|
+
if (outmask & Cs.GEODESICSCALE):
|
|
1094
|
+
M12 = M21 = p.ssig1 * p.ssig2 + \
|
|
1095
|
+
p.csig1 * p.csig2
|
|
1096
|
+
t = (p.cbet1 - p.cbet2) * self.ep2 * \
|
|
1097
|
+
(p.cbet1 + p.cbet2) / (p.dn1 + p.dn2)
|
|
1098
|
+
M12 += (p.ssig2 * t + p.csig2 * J12) * p.ssig1 / p.dn1
|
|
1099
|
+
M21 -= (p.ssig1 * t + p.csig1 * J12) * p.ssig2 / p.dn2
|
|
1100
|
+
|
|
1101
|
+
return s12b, m12b, m0, M12, M21
|
|
1102
|
+
|
|
1103
|
+
def Line(self, lat1, lon1, azi1, caps=Caps.ALL, name=NN):
|
|
1104
|
+
'''Set up a L{GeodesicLineExact} to compute several points
|
|
1105
|
+
on a single geodesic.
|
|
1106
|
+
|
|
1107
|
+
@arg lat1: Latitude of the first point (C{degrees}).
|
|
1108
|
+
@arg lon1: Longitude of the first point (C{degrees}).
|
|
1109
|
+
@arg azi1: Azimuth at the first point (compass C{degrees}).
|
|
1110
|
+
@kwarg caps: Bit-or'ed combination of L{Caps} values specifying
|
|
1111
|
+
the capabilities the L{GeodesicLineExact} instance
|
|
1112
|
+
should possess, i.e., which quantities can be
|
|
1113
|
+
returnedby calls to L{GeodesicLineExact.Position}
|
|
1114
|
+
and L{GeodesicLineExact.ArcPosition}.
|
|
1115
|
+
|
|
1116
|
+
@return: A L{GeodesicLineExact} instance.
|
|
1117
|
+
|
|
1118
|
+
@note: If the point is at a pole, the azimuth is defined by keeping
|
|
1119
|
+
B{C{lon1}} fixed, writing C{B{lat1} = ±(90 − ε)}, and taking
|
|
1120
|
+
the limit C{ε → 0+}.
|
|
1121
|
+
|
|
1122
|
+
@see: C++ U{GeodesicExact.Line
|
|
1123
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>}
|
|
1124
|
+
and Python U{Geodesic.Line<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.
|
|
1125
|
+
'''
|
|
1126
|
+
return _GeodesicLineExact(self, lat1, lon1, azi1, caps, self._debug, name=name)
|
|
1127
|
+
|
|
1128
|
+
@Property_RO
|
|
1129
|
+
def n(self):
|
|
1130
|
+
'''Get the C{ellipsoid}'s I{3rd flattening} (C{scalar}), M{f / (2 - f) == (a - b) / (a + b)}.
|
|
1131
|
+
'''
|
|
1132
|
+
return self.ellipsoid.n
|
|
1133
|
+
|
|
1134
|
+
@Property_RO
|
|
1135
|
+
def _n_0_1(self):
|
|
1136
|
+
'''(INTERNAL) Cached once.
|
|
1137
|
+
'''
|
|
1138
|
+
return fabs(self.n) > _0_1
|
|
1139
|
+
|
|
1140
|
+
@Property_RO
|
|
1141
|
+
def _n6PI(self):
|
|
1142
|
+
'''(INTERNAL) Cached once.
|
|
1143
|
+
'''
|
|
1144
|
+
return fabs(self.n) * _6_0 * PI
|
|
1145
|
+
|
|
1146
|
+
def _Newton6(self, salp1, calp1, p):
|
|
1147
|
+
'''(INTERNAL) Split off from C{_GDictInverse} to reduce complexity/length.
|
|
1148
|
+
|
|
1149
|
+
@return: 6-Tuple C{(sig12, salp1, calp1, salp2, calp2, domg12)}
|
|
1150
|
+
and C{p.iter} and C{p.trip} updated.
|
|
1151
|
+
'''
|
|
1152
|
+
_abs = fabs
|
|
1153
|
+
# This is a straightforward solution of f(alp1) = lambda12(alp1) -
|
|
1154
|
+
# lam12 = 0 with one wrinkle. f(alp) has exactly one root in the
|
|
1155
|
+
# interval (0, PI) and its derivative is positive at the root.
|
|
1156
|
+
# Thus f(alp) is positive for alp > alp1 and negative for alp < alp1.
|
|
1157
|
+
# During the course of the iteration, a range (alp1a, alp1b) is
|
|
1158
|
+
# maintained which brackets the root and with each evaluation of
|
|
1159
|
+
# f(alp) the range is shrunk, if possible. Newton's method is
|
|
1160
|
+
# restarted whenever the derivative of f is negative (because the
|
|
1161
|
+
# new value of alp1 is then further from the solution) or if the
|
|
1162
|
+
# new estimate of alp1 lies outside (0,PI); in this case, the new
|
|
1163
|
+
# starting guess is taken to be (alp1a + alp1b) / 2.
|
|
1164
|
+
salp1a = salp1b = _TINY
|
|
1165
|
+
calp1a, calp1b = _1_0, _N_1_0
|
|
1166
|
+
MAXIT1, TOL0 = _MAXIT1, _TOL0
|
|
1167
|
+
HALF, TOLb = _0_5, _TOLb
|
|
1168
|
+
tripb, TOLv = False, TOL0
|
|
1169
|
+
for i in range(_MAXIT2):
|
|
1170
|
+
# 1/4 meridian = 10e6 meter and random input,
|
|
1171
|
+
# estimated max error in nm (nano meter, by
|
|
1172
|
+
# checking Inverse problem by Direct).
|
|
1173
|
+
#
|
|
1174
|
+
# max iterations
|
|
1175
|
+
# log2(b/a) error mean sd
|
|
1176
|
+
# -7 387 5.33 3.68
|
|
1177
|
+
# -6 345 5.19 3.43
|
|
1178
|
+
# -5 269 5.00 3.05
|
|
1179
|
+
# -4 210 4.76 2.44
|
|
1180
|
+
# -3 115 4.55 1.87
|
|
1181
|
+
# -2 69 4.35 1.38
|
|
1182
|
+
# -1 36 4.05 1.03
|
|
1183
|
+
# 0 15 0.01 0.13
|
|
1184
|
+
# 1 25 5.10 1.53
|
|
1185
|
+
# 2 96 5.61 2.09
|
|
1186
|
+
# 3 318 6.02 2.74
|
|
1187
|
+
# 4 985 6.24 3.22
|
|
1188
|
+
# 5 2352 6.32 3.44
|
|
1189
|
+
# 6 6008 6.30 3.45
|
|
1190
|
+
# 7 19024 6.19 3.30
|
|
1191
|
+
v, sig12, salp2, calp2, \
|
|
1192
|
+
domg12, dv = self._Lambda6(salp1, calp1, i < MAXIT1, p)
|
|
1193
|
+
|
|
1194
|
+
# 2 * _TOL0 is approximately 1 ulp [0, PI]
|
|
1195
|
+
# reversed test to allow escape with NaNs
|
|
1196
|
+
if tripb or _abs(v) < TOLv:
|
|
1197
|
+
break
|
|
1198
|
+
# update bracketing values
|
|
1199
|
+
if v > 0 and (i > MAXIT1 or (calp1 / salp1) > (calp1b / salp1b)):
|
|
1200
|
+
salp1b, calp1b = salp1, calp1
|
|
1201
|
+
elif v < 0 and (i > MAXIT1 or (calp1 / salp1) < (calp1a / salp1a)):
|
|
1202
|
+
salp1a, calp1a = salp1, calp1
|
|
1203
|
+
|
|
1204
|
+
if i < MAXIT1 and dv > 0:
|
|
1205
|
+
dalp1 = -v / dv
|
|
1206
|
+
if _abs(dalp1) < PI:
|
|
1207
|
+
s, c = _sincos2(dalp1)
|
|
1208
|
+
# nalp1 = alp1 + dalp1
|
|
1209
|
+
s, c = _sincos12(-s, c, salp1, calp1)
|
|
1210
|
+
if s > 0:
|
|
1211
|
+
salp1, calp1 = _norm2(s, c)
|
|
1212
|
+
# in some regimes we don't get quadratic convergence
|
|
1213
|
+
# because slope -> 0. So use convergence conditions
|
|
1214
|
+
# based on epsilon instead of sqrt(epsilon)
|
|
1215
|
+
TOLv = TOL0 if _abs(v) > _TOL016 else _TOL08
|
|
1216
|
+
continue
|
|
1217
|
+
TOLv = TOL0
|
|
1218
|
+
# Either dv was not positive or updated value was outside
|
|
1219
|
+
# legal range. Use the midpoint of the bracket as the next
|
|
1220
|
+
# estimate. This mechanism is not needed for the WGS84
|
|
1221
|
+
# ellipsoid, but it does catch problems with more eccentric
|
|
1222
|
+
# ellipsoids. Its efficacy is such for the WGS84 test set
|
|
1223
|
+
# with the starting guess set to alp1 = 90 deg: the WGS84
|
|
1224
|
+
# test set: mean = 5.21, stdev = 3.93, max = 24 and WGS84
|
|
1225
|
+
# with random input: mean = 4.74, stdev = 0.99
|
|
1226
|
+
salp1, calp1 = _norm2((salp1a + salp1b) * HALF,
|
|
1227
|
+
(calp1a + calp1b) * HALF)
|
|
1228
|
+
tripb = fsum1f_(calp1a, -calp1, _abs(salp1a - salp1)) < TOLb or \
|
|
1229
|
+
fsum1f_(calp1b, -calp1, _abs(salp1b - salp1)) < TOLb
|
|
1230
|
+
else:
|
|
1231
|
+
raise GeodesicError(Fmt.no_convergence(v, TOLv), txt=repr(self)) # self.toRepr()
|
|
1232
|
+
|
|
1233
|
+
p.set_(iter=i, trip=tripb) # like .geodsolve._GDictInvoke: iter NOT iteration!
|
|
1234
|
+
return sig12, salp1, calp1, salp2, calp2, domg12
|
|
1235
|
+
|
|
1236
|
+
Polygon = Area # for C{geographiclib} compatibility
|
|
1237
|
+
|
|
1238
|
+
def _sinf1cos2d(self, lat):
|
|
1239
|
+
'''(INTERNAL) Helper, also for C{_G_GeodesicLineExact}.
|
|
1240
|
+
'''
|
|
1241
|
+
sbet, cbet = _sincos2d(lat)
|
|
1242
|
+
# ensure cbet1 = +epsilon at poles; doing the fix on beta means
|
|
1243
|
+
# that sig12 will be <= 2*tiny for two points at the same pole
|
|
1244
|
+
sbet, cbet = _norm2(sbet * self.f1, cbet)
|
|
1245
|
+
return sbet, (cbet if fabs(cbet) > _TINY else _TINY)
|
|
1246
|
+
|
|
1247
|
+
def toStr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature
|
|
1248
|
+
'''Return this C{GeodesicExact} as string.
|
|
1249
|
+
|
|
1250
|
+
@kwarg prec: The C{float} precision, number of decimal digits (0..9).
|
|
1251
|
+
Trailing zero decimals are stripped for B{C{prec}} values
|
|
1252
|
+
of 1 and above, but kept for negative B{C{prec}} values.
|
|
1253
|
+
@kwarg sep: Separator to join (C{str}).
|
|
1254
|
+
|
|
1255
|
+
@return: Tuple items (C{str}).
|
|
1256
|
+
'''
|
|
1257
|
+
d = dict(ellipsoid=self.ellipsoid, C4order=self.C4order)
|
|
1258
|
+
return sep.join(pairs(d, prec=prec))
|
|
1259
|
+
|
|
1260
|
+
|
|
1261
|
+
class GeodesicLineExact(_GeodesicLineExact):
|
|
1262
|
+
'''A pure Python version of I{Karney}'s C++ class U{GeodesicLineExact
|
|
1263
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicLineExact.html>},
|
|
1264
|
+
modeled after I{Karney}'s Python class U{geodesicline.GeodesicLine<https://GitHub.com/
|
|
1265
|
+
geographiclib/geographiclib-python>}.
|
|
1266
|
+
'''
|
|
1267
|
+
|
|
1268
|
+
def __init__(self, geodesic, lat1, lon1, azi1, caps=Caps.STANDARD, name=NN):
|
|
1269
|
+
'''New L{GeodesicLineExact} instance, allowing points to be found along
|
|
1270
|
+
a geodesic starting at C{(B{lat1}, B{lon1})} with azimuth B{C{azi1}}.
|
|
1271
|
+
|
|
1272
|
+
@arg geodesic: The geodesic to use (L{GeodesicExact}).
|
|
1273
|
+
@arg lat1: Latitude of the first point (C{degrees}).
|
|
1274
|
+
@arg lon1: Longitude of the first point (C{degrees}).
|
|
1275
|
+
@arg azi1: Azimuth at the first points (compass C{degrees}).
|
|
1276
|
+
@kwarg caps: Bit-or'ed combination of L{Caps} values specifying
|
|
1277
|
+
the capabilities the L{GeodesicLineExact} instance
|
|
1278
|
+
should possess, i.e., which quantities can be
|
|
1279
|
+
returned by calls to L{GeodesicLineExact.Position}
|
|
1280
|
+
and L{GeodesicLineExact.ArcPosition}.
|
|
1281
|
+
@kwarg name: Optional name (C{str}).
|
|
1282
|
+
|
|
1283
|
+
@raise TypeError: Invalid B{C{geodesic}}.
|
|
1284
|
+
'''
|
|
1285
|
+
_xinstanceof(GeodesicExact, geodesic=geodesic)
|
|
1286
|
+
if (caps & Caps.LINE_OFF): # copy to avoid updates
|
|
1287
|
+
geodesic = geodesic.copy(deep=False, name=NN(_UNDER_, geodesic.name))
|
|
1288
|
+
# _update_all(geodesic)
|
|
1289
|
+
_GeodesicLineExact.__init__(self, geodesic, lat1, lon1, azi1, caps, 0, name=name)
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
def _Astroid(x, y):
|
|
1293
|
+
'''(INTERNAL) Solve M{k^4 + 2 * k^3 - (x^2 + y^2 - 1)
|
|
1294
|
+
* k^2 - (2 * k + 1) * y^2 = 0} for positive root k.
|
|
1295
|
+
'''
|
|
1296
|
+
p = x**2
|
|
1297
|
+
q = y**2
|
|
1298
|
+
r = fsumf_(_1_0, q, p, _N_2_0)
|
|
1299
|
+
if r > 0 or q:
|
|
1300
|
+
# avoid possible division by zero when r = 0
|
|
1301
|
+
# by multiplying s and t by r^3 and r, resp.
|
|
1302
|
+
S = p * q / _4_0 # S = r^3 * s
|
|
1303
|
+
if r:
|
|
1304
|
+
r = r / _6_0 # /= chokes PyChecker
|
|
1305
|
+
r3 = r**3
|
|
1306
|
+
T3 = r3 + S
|
|
1307
|
+
# discriminant of the quadratic equation for T3 is
|
|
1308
|
+
# zero on the evolute curve p^(1/3) + q^(1/3) = 1
|
|
1309
|
+
d = (r3 + T3) * S
|
|
1310
|
+
if d < 0:
|
|
1311
|
+
# T is complex, but u is defined for a real result
|
|
1312
|
+
a = atan2(sqrt(-d), -T3) / _3_0
|
|
1313
|
+
# There are 3 possible cube roots, choose the one which
|
|
1314
|
+
# avoids cancellation. Note d < 0 implies that r < 0.
|
|
1315
|
+
u = (cos(a) * _2_0 + _1_0) * r
|
|
1316
|
+
else:
|
|
1317
|
+
# pick the sign on the sqrt to maximize abs(T3) to
|
|
1318
|
+
# minimize loss of precision due to cancellation.
|
|
1319
|
+
if d:
|
|
1320
|
+
T3 += _copysign(sqrt(d), T3) # T3 = (r * t)^3
|
|
1321
|
+
# _cbrt always returns the real root, _cbrt(-8) = -2
|
|
1322
|
+
u = _cbrt(T3) # T = r * t
|
|
1323
|
+
if u: # T can be zero; but then r2 / T -> 0
|
|
1324
|
+
u += r**2 / u
|
|
1325
|
+
u += r
|
|
1326
|
+
elif S: # d == T3**2 == S**2: sqrt(d) == abs(S) == abs(T3)
|
|
1327
|
+
u = _cbrt(S * _2_0) # == T3 + _copysign(abs(S), T3)
|
|
1328
|
+
else:
|
|
1329
|
+
u = _0_0
|
|
1330
|
+
v = _hypot(u, y) # sqrt(u**2 + q)
|
|
1331
|
+
# avoid loss of accuracy when u < 0
|
|
1332
|
+
u = (q / (v - u)) if u < 0 else (v + u)
|
|
1333
|
+
w = (u - q) / (v + v) # positive?
|
|
1334
|
+
# rearrange expression for k to avoid loss of accuracy due to
|
|
1335
|
+
# subtraction, division by 0 impossible because u > 0, w >= 0
|
|
1336
|
+
k = u / (sqrt(w**2 + u) + w) # guaranteed positive
|
|
1337
|
+
|
|
1338
|
+
else: # q == 0 && r <= 0
|
|
1339
|
+
# y = 0 with |x| <= 1. Handle this case directly, for
|
|
1340
|
+
# y small, positive root is k = abs(y) / sqrt(1 - x^2)
|
|
1341
|
+
k = _0_0
|
|
1342
|
+
|
|
1343
|
+
return k
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
def _C4coeffs(nC4): # in .geodesicx.__main__
|
|
1347
|
+
'''(INTERNAL) Get the C{C4_24}, C{_27} or C{_30} series coefficients.
|
|
1348
|
+
'''
|
|
1349
|
+
try: # from pygeodesy.geodesicx._C4_xx import _coeffs_xx as _coeffs
|
|
1350
|
+
_C4_xx = _DOT_(_MODS.geodesicx.__name__, _UNDER_('_C4', nC4))
|
|
1351
|
+
_coeffs = _MODS.getattr(_C4_xx, _UNDER_('_coeffs', nC4))
|
|
1352
|
+
except (AttributeError, ImportError, TypeError) as x:
|
|
1353
|
+
raise GeodesicError(nC4=nC4, cause=x)
|
|
1354
|
+
n = _xnC4(nC4=nC4)
|
|
1355
|
+
if len(_coeffs) != n: # double check
|
|
1356
|
+
raise GeodesicError(_coeffs=len(_coeffs), _xnC4=n, nC4=nC4)
|
|
1357
|
+
return _coeffs
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
__all__ += _ALL_DOCS(GeodesicExact, GeodesicLineExact)
|
|
1361
|
+
|
|
1362
|
+
# **) MIT License
|
|
1363
|
+
#
|
|
1364
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
1365
|
+
#
|
|
1366
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
1367
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
1368
|
+
# to deal in the Software without restriction, including without limitation
|
|
1369
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
1370
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
1371
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
1372
|
+
#
|
|
1373
|
+
# The above copyright notice and this permission notice shall be included
|
|
1374
|
+
# in all copies or substantial portions of the Software.
|
|
1375
|
+
#
|
|
1376
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
1377
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1378
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
1379
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
1380
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
1381
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
1382
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|