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/ltp.py
ADDED
|
@@ -0,0 +1,1107 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''I{Local Tangent Plane} (LTP) and I{local} cartesian coordinates.
|
|
5
|
+
|
|
6
|
+
I{Local cartesian} and I{local tangent plane} classes L{LocalCartesian}, approximations L{ChLVa}
|
|
7
|
+
and L{ChLVe} and L{Ltp}, L{ChLV}, L{LocalError}, L{Attitude} and L{Frustum}.
|
|
8
|
+
|
|
9
|
+
@see: U{Local tangent plane coordinates<https://WikiPedia.org/wiki/Local_tangent_plane_coordinates>}
|
|
10
|
+
and class L{LocalCartesian}, transcoded from I{Charles Karney}'s C++ classU{LocalCartesian
|
|
11
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1LocalCartesian.html>}.
|
|
12
|
+
'''
|
|
13
|
+
# make sure int/int division yields float quotient, see .basics
|
|
14
|
+
from __future__ import division as _; del _ # PYCHOK semicolon
|
|
15
|
+
|
|
16
|
+
from pygeodesy.basics import _args_kwds_names, issubclassof, map1, map2 # .datums
|
|
17
|
+
from pygeodesy.constants import EPS, INT0, _umod_360, _0_0, _0_01, _0_5, _1_0, \
|
|
18
|
+
_2_0, _60_0, _90_0, _100_0, _180_0, _3600_0, \
|
|
19
|
+
_N_1_0 # PYCHOK used!
|
|
20
|
+
from pygeodesy.datums import _WGS84, _xinstanceof
|
|
21
|
+
from pygeodesy.ecef import _EcefBase, EcefKarney, _llhn4, _xyzn4
|
|
22
|
+
from pygeodesy.errors import _NotImplementedError, _TypesError, _ValueError, \
|
|
23
|
+
_xattr, _xkwds, _xkwds_get
|
|
24
|
+
from pygeodesy.fmath import fabs, fdot, Fhorner
|
|
25
|
+
from pygeodesy.fsums import _floor, Fsum, fsumf_, fsum1f_
|
|
26
|
+
from pygeodesy.interns import NN, _0_, _COMMASPACE_, _DOT_, _ecef_, _height_, \
|
|
27
|
+
_invalid_, _lat0_, _lon0_, _ltp_, _M_, _name_, _too_
|
|
28
|
+
# from pygeodesy.lazily import _ALL_LAZY # from vector3d
|
|
29
|
+
from pygeodesy.ltpTuples import Attitude4Tuple, ChLVEN2Tuple, ChLV9Tuple, \
|
|
30
|
+
ChLVYX2Tuple, Footprint5Tuple, Local9Tuple, \
|
|
31
|
+
ChLVyx2Tuple, _XyzLocals4, _XyzLocals5, Xyz4Tuple
|
|
32
|
+
from pygeodesy.named import _NamedBase, notOverloaded
|
|
33
|
+
from pygeodesy.namedTuples import LatLon3Tuple, LatLon4Tuple, Vector3Tuple
|
|
34
|
+
from pygeodesy.props import Property, Property_RO, property_doc_, property_RO, \
|
|
35
|
+
_update_all
|
|
36
|
+
from pygeodesy.streprs import Fmt, strs, unstr
|
|
37
|
+
from pygeodesy.units import Bearing, Degrees, _isHeight, Meter
|
|
38
|
+
from pygeodesy.utily import cotd, _loneg, sincos2d, sincos2d_, tand, tand_, \
|
|
39
|
+
wrap180, wrap360
|
|
40
|
+
from pygeodesy.vector3d import _ALL_LAZY, Vector3d
|
|
41
|
+
|
|
42
|
+
# from math import fabs, floor as _floor # from .fmath, .fsums
|
|
43
|
+
|
|
44
|
+
__all__ = _ALL_LAZY.ltp
|
|
45
|
+
__version__ = '24.02.16'
|
|
46
|
+
|
|
47
|
+
_height0_ = _height_ + _0_
|
|
48
|
+
_narrow_ = 'narrow'
|
|
49
|
+
_wide_ = 'wide'
|
|
50
|
+
_Xyz_ = 'Xyz'
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _fov_2(**fov):
|
|
54
|
+
# Half a field-of-view angle in C{degrees}.
|
|
55
|
+
f = Degrees(Error=LocalError, **fov) * _0_5
|
|
56
|
+
if EPS < f < _90_0:
|
|
57
|
+
return f
|
|
58
|
+
t = _invalid_ if f < 0 else _too_(_wide_ if f > EPS else _narrow_)
|
|
59
|
+
raise LocalError(txt=t, **fov)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Attitude(_NamedBase):
|
|
63
|
+
'''The orientation of a plane or camera in space.
|
|
64
|
+
'''
|
|
65
|
+
_alt = Meter( alt =_0_0)
|
|
66
|
+
_roll = Degrees(roll=_0_0)
|
|
67
|
+
_tilt = Degrees(tilt=_0_0)
|
|
68
|
+
_yaw = Bearing(yaw =_0_0)
|
|
69
|
+
|
|
70
|
+
def __init__(self, alt_attitude=INT0, tilt=INT0, yaw=INT0, roll=INT0, name=NN):
|
|
71
|
+
'''New L{Attitude}.
|
|
72
|
+
|
|
73
|
+
@kwarg alt_attitude: An altitude (C{meter}) above earth or an attitude
|
|
74
|
+
(L{Attitude} or L{Attitude4Tuple}) with the
|
|
75
|
+
C{B{alt}itude}, B{C{tilt}}, B{C{yaw}} and B{C{roll}}.
|
|
76
|
+
@kwarg tilt: Pitch, elevation from horizontal (C{degrees180}), negative down
|
|
77
|
+
(clockwise rotation along and around the x- or East axis).
|
|
78
|
+
@kwarg yaw: Bearing, heading (compass C{degrees360}), clockwise from North
|
|
79
|
+
(counter-clockwise rotation along and around the z- or Up axis).
|
|
80
|
+
@kwarg roll: Roll, bank (C{degrees180}), positive to the right and down
|
|
81
|
+
(clockwise rotation along and around the y- or North axis).
|
|
82
|
+
@kwarg name: Optional name C{str}).
|
|
83
|
+
|
|
84
|
+
@raise AttitudeError: Invalid B{C{alt_attitude}}, B{C{tilt}}, B{C{yaw}} or
|
|
85
|
+
B{C{roll}}.
|
|
86
|
+
|
|
87
|
+
@see: U{Principal axes<https://WikiPedia.org/wiki/Aircraft_principal_axes>} and
|
|
88
|
+
U{Yaw, pitch, and roll rotations<http://MSL.CS.UIUC.edu/planning/node102.html>}.
|
|
89
|
+
'''
|
|
90
|
+
if _isHeight(alt_attitude):
|
|
91
|
+
t = Attitude4Tuple(alt_attitude, tilt, yaw, roll)
|
|
92
|
+
else:
|
|
93
|
+
try:
|
|
94
|
+
t = alt_attitude.atyr
|
|
95
|
+
except AttributeError:
|
|
96
|
+
raise AttitudeError(alt=alt_attitude, tilt=tilt, yaw=yaw, rol=roll)
|
|
97
|
+
for n, v in t.items():
|
|
98
|
+
if v:
|
|
99
|
+
setattr(self, n, v)
|
|
100
|
+
n = name or t.name
|
|
101
|
+
if n:
|
|
102
|
+
self.name = n
|
|
103
|
+
|
|
104
|
+
@property_doc_(' altitude above earth in C{meter}.')
|
|
105
|
+
def alt(self):
|
|
106
|
+
return self._alt
|
|
107
|
+
|
|
108
|
+
@alt.setter # PYCHOK setter!
|
|
109
|
+
def alt(self, alt): # PYCHOK no cover
|
|
110
|
+
a = Meter(alt=alt, Error=AttitudeError)
|
|
111
|
+
if self._alt != a:
|
|
112
|
+
_update_all(self)
|
|
113
|
+
self._alt = a
|
|
114
|
+
|
|
115
|
+
altitude = alt
|
|
116
|
+
|
|
117
|
+
@Property_RO
|
|
118
|
+
def atyr(self):
|
|
119
|
+
'''Return this attitude's alt[itude], tilt, yaw and roll as an L{Attitude4Tuple}.
|
|
120
|
+
'''
|
|
121
|
+
return Attitude4Tuple(self.alt, self.tilt, self.yaw, self.roll, name=self.name)
|
|
122
|
+
|
|
123
|
+
@Property_RO
|
|
124
|
+
def matrix(self):
|
|
125
|
+
'''Get the 3x3 rotation matrix C{R(yaw)·R(tilt)·R(roll)}, aka I{ZYX} (C{float}, row-order).
|
|
126
|
+
|
|
127
|
+
@see: Matrix M of case 10 in U{Appendix A
|
|
128
|
+
<https://ntrs.NASA.gov/api/citations/19770019231/downloads/19770019231.pdf>}.
|
|
129
|
+
'''
|
|
130
|
+
_f = fsum1f_
|
|
131
|
+
# to follow the definitions of rotation angles alpha, beta and gamma:
|
|
132
|
+
# negate yaw since yaw is counter-clockwise around the z-axis, swap
|
|
133
|
+
# tilt and roll since tilt is around the x- and roll around the y-axis
|
|
134
|
+
sa, ca, sb, cb, sg, cg = sincos2d_(-self.yaw, self.roll, self.tilt)
|
|
135
|
+
return ((ca * cb, _f(ca * sb * sg, -sa * cg), _f(ca * sb * cg, sa * sg)),
|
|
136
|
+
(sa * cb, _f(sa * sb * sg, ca * cg), _f(sa * sb * cg, -ca * sg)),
|
|
137
|
+
( -sb, cb * sg, cb * cg))
|
|
138
|
+
|
|
139
|
+
@property_doc_(' roll/bank in C{degrees180}, positive to the right and down.')
|
|
140
|
+
def roll(self):
|
|
141
|
+
return self._roll
|
|
142
|
+
|
|
143
|
+
@roll.setter # PYCHOK setter!
|
|
144
|
+
def roll(self, roll):
|
|
145
|
+
r = Degrees(roll=roll, wrap=wrap180, Error=AttitudeError)
|
|
146
|
+
if self._roll != r:
|
|
147
|
+
_update_all(self)
|
|
148
|
+
self._roll = r
|
|
149
|
+
|
|
150
|
+
bank = roll
|
|
151
|
+
|
|
152
|
+
def rotate(self, x_xyz, y=None, z=None, Vector=None, **Vector_kwds):
|
|
153
|
+
'''Transform a (local) cartesian by this attitude's matrix.
|
|
154
|
+
|
|
155
|
+
@arg x_xyz: X component of vector (C{scalar}) or (3-D) vector
|
|
156
|
+
(C{Cartesian}, L{Vector3d} or L{Vector3Tuple}).
|
|
157
|
+
@kwarg y: Y component of vector (C{scalar}), same units as B{C{x}}.
|
|
158
|
+
@kwarg z: Z component of vector (C{scalar}), same units as B{C{x}}.
|
|
159
|
+
@kwarg Vector: Class to return transformed point (C{Cartesian},
|
|
160
|
+
L{Vector3d} or C{Vector3Tuple}) or C{None}.
|
|
161
|
+
@kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword arguments,
|
|
162
|
+
ignored if C{B{Vector} is None}.
|
|
163
|
+
|
|
164
|
+
@return: A B{C{Vector}} instance or a L{Vector3Tuple}C{(x, y, z)} if
|
|
165
|
+
C{B{Vector}=None}.
|
|
166
|
+
|
|
167
|
+
@raise AttitudeError: Invalid B{C{x_xyz}}, B{C{y}} or B{C{z}}.
|
|
168
|
+
|
|
169
|
+
@see: U{Yaw, pitch, and roll rotations<http://MSL.CS.UIUC.edu/planning/node102.html>}.
|
|
170
|
+
'''
|
|
171
|
+
try:
|
|
172
|
+
try:
|
|
173
|
+
xyz = map2(float, x_xyz.xyz)
|
|
174
|
+
except AttributeError:
|
|
175
|
+
xyz = map1(float, x_xyz, y, z)
|
|
176
|
+
except (TypeError, ValueError) as x:
|
|
177
|
+
raise AttitudeError(x_xyz=x_xyz, y=y, z=z, cause=x)
|
|
178
|
+
|
|
179
|
+
x, y, z = (fdot(r, *xyz) for r in self.matrix)
|
|
180
|
+
return Vector3Tuple(x, y, z, name=self.name) if Vector is None else \
|
|
181
|
+
Vector(x, y, z, **_xkwds(Vector_kwds, name=self.name))
|
|
182
|
+
|
|
183
|
+
@property_doc_(' tilt/pitch/elevation from horizontal in C{degrees180}, negative down.')
|
|
184
|
+
def tilt(self):
|
|
185
|
+
return self._tilt
|
|
186
|
+
|
|
187
|
+
@tilt.setter # PYCHOK setter!
|
|
188
|
+
def tilt(self, tilt):
|
|
189
|
+
t = Degrees(tilt=tilt, wrap=wrap180, Error=AttitudeError)
|
|
190
|
+
if self._tilt != t:
|
|
191
|
+
_update_all(self)
|
|
192
|
+
self._tilt = t
|
|
193
|
+
|
|
194
|
+
elevation = pitch = tilt
|
|
195
|
+
|
|
196
|
+
def toStr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature
|
|
197
|
+
'''Format this attitude as string.
|
|
198
|
+
|
|
199
|
+
@kwarg prec: The C{float} precision, number of decimal digits (0..9).
|
|
200
|
+
Trailing zero decimals are stripped for B{C{prec}} values
|
|
201
|
+
of 1 and above, but kept for negative B{C{prec}} values.
|
|
202
|
+
@kwarg sep: Separator to join (C{str}).
|
|
203
|
+
|
|
204
|
+
@return: This attitude (C{str}).
|
|
205
|
+
'''
|
|
206
|
+
return self.atyr.toStr(prec=prec, sep=sep)
|
|
207
|
+
|
|
208
|
+
@Property_RO
|
|
209
|
+
def tyr3d(self):
|
|
210
|
+
'''Get this attitude's (3-D) directional vector (L{Vector3d}).
|
|
211
|
+
|
|
212
|
+
@see: U{Yaw, pitch, and roll rotations<http://MSL.CS.UIUC.edu/planning/node102.html>}.
|
|
213
|
+
'''
|
|
214
|
+
def _r2d(r):
|
|
215
|
+
return fsumf_(_N_1_0, *r)
|
|
216
|
+
|
|
217
|
+
return Vector3d(*map(_r2d, self.matrix), name=tyr3d.__name__)
|
|
218
|
+
|
|
219
|
+
@property_doc_(' yaw/bearing/heading in compass C{degrees360}, clockwise from North.')
|
|
220
|
+
def yaw(self):
|
|
221
|
+
return self._yaw
|
|
222
|
+
|
|
223
|
+
@yaw.setter # PYCHOK setter!
|
|
224
|
+
def yaw(self, yaw):
|
|
225
|
+
y = Bearing(yaw=yaw, Error=AttitudeError)
|
|
226
|
+
if self._yaw != y:
|
|
227
|
+
_update_all(self)
|
|
228
|
+
self._yaw = y
|
|
229
|
+
|
|
230
|
+
bearing = heading = yaw
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class AttitudeError(_ValueError):
|
|
234
|
+
'''An L{Attitude} or L{Attitude4Tuple} issue.
|
|
235
|
+
'''
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class Frustum(_NamedBase):
|
|
240
|
+
'''A rectangular pyramid, typically representing a camera's I{field-of-view}
|
|
241
|
+
(fov) and the intersection with (or projection to) a I{local tangent plane}.
|
|
242
|
+
|
|
243
|
+
@see: U{Viewing frustum<https://WikiPedia.org/wiki/Viewing_frustum>}.
|
|
244
|
+
'''
|
|
245
|
+
_h_2 = _0_0 # half hfov in degrees
|
|
246
|
+
_ltp = None # local tangent plane
|
|
247
|
+
_tan_h_2 = _0_0 # tan(_h_2)
|
|
248
|
+
_v_2 = _0_0 # half vfov in degrees
|
|
249
|
+
|
|
250
|
+
def __init__(self, hfov, vfov, ltp=None):
|
|
251
|
+
'''New L{Frustum}.
|
|
252
|
+
|
|
253
|
+
@arg hfov: Horizontal field-of-view (C{degrees180}).
|
|
254
|
+
@arg vfov: Vertical field-of-view (C{degrees180}).
|
|
255
|
+
@kwarg ltp: Optional I{local tangent plane} (L{Ltp}).
|
|
256
|
+
|
|
257
|
+
@raise LocalError: Invalid B{C{hfov}} or B{C{vfov}}.
|
|
258
|
+
'''
|
|
259
|
+
self._h_2 = h = _fov_2(hfov=hfov)
|
|
260
|
+
self._v_2 = _fov_2(vfov=vfov)
|
|
261
|
+
|
|
262
|
+
self._tan_h_2 = tand(h, hfov_2=h)
|
|
263
|
+
|
|
264
|
+
if ltp:
|
|
265
|
+
self._ltp = _xLtp(ltp)
|
|
266
|
+
|
|
267
|
+
def footprint5(self, alt_attitude, tilt=0, yaw=0, roll=0, z=_0_0, ltp=None): # MCCABE 15
|
|
268
|
+
'''Compute the center and corners of the intersection with (or projection
|
|
269
|
+
to) the I{local tangent plane} (LTP).
|
|
270
|
+
|
|
271
|
+
@arg alt_attitude: An altitude (C{meter}) above I{local tangent plane} or
|
|
272
|
+
an attitude (L{Attitude} or L{Attitude4Tuple}) with the
|
|
273
|
+
C{B{alt}itude}, B{C{tilt}}, B{C{yaw}} and B{C{roll}}.
|
|
274
|
+
@kwarg tilt: Pitch, elevation from horizontal (C{degrees}), negative down
|
|
275
|
+
(clockwise rotation along and around the x- or East axis).
|
|
276
|
+
@kwarg yaw: Bearing, heading (compass C{degrees}), clockwise from North
|
|
277
|
+
(counter-clockwise rotation along and around the z- or Up axis).
|
|
278
|
+
@kwarg roll: Roll, bank (C{degrees}), positive to the right and down
|
|
279
|
+
(clockwise rotation along and around the y- or North axis).
|
|
280
|
+
@kwarg z: Optional height of the footprint (C{meter}) above I{local tangent plane}.
|
|
281
|
+
@kwarg ltp: The I{local tangent plane} (L{Ltp}), overriding this
|
|
282
|
+
frustum's C{ltp}.
|
|
283
|
+
|
|
284
|
+
@return: A L{Footprint5Tuple}C{(center, upperleft, upperight, loweright,
|
|
285
|
+
lowerleft)} with the C{center} and 4 corners, each an L{Xyz4Tuple}.
|
|
286
|
+
|
|
287
|
+
@raise TypeError: Invalid B{C{ltp}}.
|
|
288
|
+
|
|
289
|
+
@raise UnitError: Invalid B{C{altitude}}, B{C{tilt}}, B{C{roll}} or B{C{z}}.
|
|
290
|
+
|
|
291
|
+
@raise ValueError: If B{C{altitude}} too low, B{C{z}} too high or B{C{tilt}}
|
|
292
|
+
or B{C{roll}} -including B{C{vfov}} respectively B{C{hfov}}-
|
|
293
|
+
over the horizon.
|
|
294
|
+
|
|
295
|
+
@see: U{Principal axes<https://WikiPedia.org/wiki/Aircraft_principal_axes>}.
|
|
296
|
+
'''
|
|
297
|
+
def _xy2(a, e, h_2, tan_h_2, r):
|
|
298
|
+
# left and right corners, or swapped
|
|
299
|
+
if r < EPS: # no roll
|
|
300
|
+
r = a * tan_h_2
|
|
301
|
+
l = -r # PYCHOK l is ell
|
|
302
|
+
else: # roll
|
|
303
|
+
r, l = tand_(r - h_2, r + h_2, roll_hfov=r) # PYCHOK l is ell
|
|
304
|
+
r *= -a # negate right positive
|
|
305
|
+
l *= -a # PYCHOK l is ell
|
|
306
|
+
y = a * cotd(e, tilt_vfov=e)
|
|
307
|
+
return (l, y), (r, y)
|
|
308
|
+
|
|
309
|
+
def _xyz5(b, xy5, z, ltp):
|
|
310
|
+
# rotate (x, y)'s by bearing, clockwise
|
|
311
|
+
s, c = sincos2d(b)
|
|
312
|
+
_f = fsum1f_
|
|
313
|
+
for x, y in xy5:
|
|
314
|
+
yield Xyz4Tuple(_f(x * c, y * s),
|
|
315
|
+
_f(y * c, -x * s), z, ltp)
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
a, t, y, r = alt_attitude.atyr
|
|
319
|
+
except AttributeError:
|
|
320
|
+
a, t, y, r = alt_attitude, tilt, yaw, roll
|
|
321
|
+
|
|
322
|
+
a = Meter(altitude=a)
|
|
323
|
+
if a < EPS: # too low
|
|
324
|
+
raise _ValueError(altitude=a)
|
|
325
|
+
if z: # PYCHOK no cover
|
|
326
|
+
z = Meter(z=z)
|
|
327
|
+
a -= z
|
|
328
|
+
if a < EPS: # z above a
|
|
329
|
+
raise _ValueError(altitude_z=a)
|
|
330
|
+
else:
|
|
331
|
+
z = _0_0
|
|
332
|
+
|
|
333
|
+
b = Degrees(yaw=y, wrap=wrap360) # bearing
|
|
334
|
+
e = -Degrees(tilt=t, wrap=wrap180) # elevation, pitch
|
|
335
|
+
if not EPS < e < _180_0:
|
|
336
|
+
raise _ValueError(tilt=t)
|
|
337
|
+
if e > _90_0:
|
|
338
|
+
e = _loneg(e)
|
|
339
|
+
b = _umod_360(b + _180_0)
|
|
340
|
+
|
|
341
|
+
r = Degrees(roll=r, wrap=wrap180) # roll center
|
|
342
|
+
x = (-a * tand(r, roll=r)) if r else _0_0
|
|
343
|
+
y = a * cotd(e, tilt=t) # ground range
|
|
344
|
+
if fabs(y) < EPS:
|
|
345
|
+
y = _0_0
|
|
346
|
+
|
|
347
|
+
v, h, t = self._v_2, self._h_2, self._tan_h_2
|
|
348
|
+
# center and corners, clockwise from upperleft, rolled
|
|
349
|
+
xy5 = ((x, y),) + _xy2(a, e - v, h, t, r) \
|
|
350
|
+
+ _xy2(a, e + v, -h, -t, r) # swapped
|
|
351
|
+
# turn center and corners by yaw, clockwise
|
|
352
|
+
p = self.ltp if ltp is None else ltp # None OK
|
|
353
|
+
return Footprint5Tuple(_xyz5(b, xy5, z, p)) # *_xyz5
|
|
354
|
+
|
|
355
|
+
@Property_RO
|
|
356
|
+
def hfov(self):
|
|
357
|
+
'''Get the horizontal C{fov} (C{degrees}).
|
|
358
|
+
'''
|
|
359
|
+
return Degrees(hfov=self._h_2 * _2_0)
|
|
360
|
+
|
|
361
|
+
@Property_RO
|
|
362
|
+
def ltp(self):
|
|
363
|
+
'''Get the I{local tangent plane} (L{Ltp}) or C{None}.
|
|
364
|
+
'''
|
|
365
|
+
return self._ltp
|
|
366
|
+
|
|
367
|
+
def toStr(self, prec=3, fmt=Fmt.F, sep=_COMMASPACE_): # PYCHOK signature
|
|
368
|
+
'''Convert this frustum to a "hfov, vfov, ltp" string.
|
|
369
|
+
|
|
370
|
+
@kwarg prec: Number of (decimal) digits, unstripped (0..8 or C{None}).
|
|
371
|
+
@kwarg fmt: Optional, C{float} format (C{letter}).
|
|
372
|
+
@kwarg sep: Separator to join (C{str}).
|
|
373
|
+
|
|
374
|
+
@return: Frustum in the specified form (C{str}).
|
|
375
|
+
'''
|
|
376
|
+
t = self.hfov, self.vfov
|
|
377
|
+
if self.ltp:
|
|
378
|
+
t += self.ltp,
|
|
379
|
+
t = strs(t, prec=prec, fmt=fmt)
|
|
380
|
+
return sep.join(t) if sep else t
|
|
381
|
+
|
|
382
|
+
@Property_RO
|
|
383
|
+
def vfov(self):
|
|
384
|
+
'''Get the vertical C{fov} (C{degrees}).
|
|
385
|
+
'''
|
|
386
|
+
return Degrees(vfov=self._v_2 * _2_0)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class LocalError(_ValueError):
|
|
390
|
+
'''A L{LocalCartesian} or L{Ltp} related issue.
|
|
391
|
+
'''
|
|
392
|
+
pass
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class LocalCartesian(_NamedBase):
|
|
396
|
+
'''Conversion between geodetic C{(lat, lon, height)} and I{local
|
|
397
|
+
cartesian} C{(x, y, z)} coordinates with I{geodetic} origin
|
|
398
|
+
C{(lat0, lon0, height0)}, transcoded from I{Karney}'s C++ class
|
|
399
|
+
U{LocalCartesian<https://GeographicLib.SourceForge.io/C++/doc/
|
|
400
|
+
classGeographicLib_1_1LocalCartesian.html>}.
|
|
401
|
+
|
|
402
|
+
The C{z} axis is normal to the ellipsoid, the C{y} axis points due
|
|
403
|
+
North. The plane C{z = -height0} is tangent to the ellipsoid.
|
|
404
|
+
|
|
405
|
+
The conversions all take place via geocentric coordinates using a
|
|
406
|
+
geocentric L{EcefKarney}, by default the WGS84 datum/ellipsoid.
|
|
407
|
+
|
|
408
|
+
@see: Class L{Ltp}.
|
|
409
|
+
'''
|
|
410
|
+
_ecef = EcefKarney(_WGS84)
|
|
411
|
+
_Ecef = EcefKarney
|
|
412
|
+
_lon00 = INT0 # self.lon0
|
|
413
|
+
_t0 = None # origin (..., lat0, lon0, height0, ...) L{Ecef9Tuple}
|
|
414
|
+
_9Tuple = Local9Tuple
|
|
415
|
+
|
|
416
|
+
def __init__(self, latlonh0=INT0, lon0=INT0, height0=INT0, ecef=None, name=NN, **lon00):
|
|
417
|
+
'''New L{LocalCartesian} converter.
|
|
418
|
+
|
|
419
|
+
@kwarg latlonh0: The (geodetic) origin (C{LatLon}, L{LatLon4Tuple}, L{Ltp}
|
|
420
|
+
L{LocalCartesian} or L{Ecef9Tuple}) or the C{scalar}
|
|
421
|
+
latitude of the (goedetic) origin (C{degrees}).
|
|
422
|
+
@kwarg lon0: Longitude of the (goedetic) origin (C{degrees}) for C{scalar}
|
|
423
|
+
B{C{latlonh0}}, ignored otherwise.
|
|
424
|
+
@kwarg height0: Optional height (C{meter}, conventionally) at the (goedetic)
|
|
425
|
+
origin perpendicular to and above (or below) the ellipsoid's
|
|
426
|
+
surface and for C{scalar} B{C{latlonh0}}, ignored otherwise.
|
|
427
|
+
@kwarg ecef: An ECEF converter (L{EcefKarney} I{only}) for C{scalar}
|
|
428
|
+
B{C{latlonh0}}, ignored otherwise.
|
|
429
|
+
@kwarg name: Optional name (C{str}).
|
|
430
|
+
@kwarg lon00: An arbitrary, I{polar} longitude (C{degrees}), overriding
|
|
431
|
+
the default C{B{lon00}=B{lon0}}, see method C{reverse}.
|
|
432
|
+
|
|
433
|
+
@raise LocalError: If B{C{latlonh0}} not C{LatLon}, L{LatLon4Tuple}, L{Ltp},
|
|
434
|
+
L{LocalCartesian} or L{Ecef9Tuple} or B{C{latlonh0}},
|
|
435
|
+
B{C{lon0}}, B{C{height0}} or B{C{lon00}} invalid.
|
|
436
|
+
|
|
437
|
+
@raise TypeError: Invalid B{C{ecef}} or not L{EcefKarney}.
|
|
438
|
+
|
|
439
|
+
@note: If BC{latlonh0} is an L{Ltp} or L{LocalCartesian}, only C{lat0}, C{lon0},
|
|
440
|
+
C{height0} and I{polar} C{lon00} are copied, I{not} the ECEF converter.
|
|
441
|
+
'''
|
|
442
|
+
self.reset(latlonh0, lon0=lon0, height0=height0, ecef=ecef, name=name, **lon00)
|
|
443
|
+
|
|
444
|
+
def __eq__(self, other):
|
|
445
|
+
'''Compare this and an other instance.
|
|
446
|
+
|
|
447
|
+
@arg other: The other ellipsoid (L{LocalCartesian} or L{Ltp}).
|
|
448
|
+
|
|
449
|
+
@return: C{True} if equal, C{False} otherwise.
|
|
450
|
+
'''
|
|
451
|
+
return other is self or (isinstance(other, self.__class__) and
|
|
452
|
+
other.ecef == self.ecef and
|
|
453
|
+
other._t0 == self._t0)
|
|
454
|
+
|
|
455
|
+
@Property_RO
|
|
456
|
+
def datum(self):
|
|
457
|
+
'''Get the ECEF converter's datum (L{Datum}).
|
|
458
|
+
'''
|
|
459
|
+
return self.ecef.datum
|
|
460
|
+
|
|
461
|
+
@Property_RO
|
|
462
|
+
def ecef(self):
|
|
463
|
+
'''Get the ECEF converter (L{EcefKarney}).
|
|
464
|
+
'''
|
|
465
|
+
return self._ecef
|
|
466
|
+
|
|
467
|
+
def _ecef2local(self, ecef, Xyz, Xyz_kwds):
|
|
468
|
+
'''(INTERNAL) Convert geocentric/geodetic to local, like I{forward}.
|
|
469
|
+
|
|
470
|
+
@arg ecef: Geocentric (and geodetic) (L{Ecef9Tuple}).
|
|
471
|
+
@arg Xyz: An L{XyzLocal}, L{Enu} or L{Ned} I{class} or C{None}.
|
|
472
|
+
@arg Xyz_kwds: B{C{Xyz}} keyword arguments, ignored if C{B{Xyz} is None}.
|
|
473
|
+
|
|
474
|
+
@return: An C{B{Xyz}(x, y, z, ltp, **B{Xyz_kwds}} instance or if
|
|
475
|
+
C{B{Xyz} is None}, a L{Local9Tuple}C{(x, y, z, lat, lon,
|
|
476
|
+
height, ltp, ecef, M)} with this C{ltp}, B{C{ecef}}
|
|
477
|
+
(L{Ecef9Tuple}) converted to this C{datum} and C{M=None},
|
|
478
|
+
always.
|
|
479
|
+
'''
|
|
480
|
+
ltp = self
|
|
481
|
+
if ecef.datum != ltp.datum:
|
|
482
|
+
ecef = ecef.toDatum(ltp.datum)
|
|
483
|
+
x, y, z = self.M.rotate(ecef.xyz, *ltp._t0_xyz)
|
|
484
|
+
r = Local9Tuple(x, y, z, ecef.lat, ecef.lon, ecef.height,
|
|
485
|
+
ltp, ecef, None, name=ecef.name)
|
|
486
|
+
if Xyz:
|
|
487
|
+
if not issubclassof(Xyz, *_XyzLocals4): # Vector3d
|
|
488
|
+
raise _TypesError(_Xyz_, Xyz, *_XyzLocals4)
|
|
489
|
+
r = r.toXyz(Xyz=Xyz, **Xyz_kwds)
|
|
490
|
+
return r
|
|
491
|
+
|
|
492
|
+
@Property_RO
|
|
493
|
+
def ellipsoid(self):
|
|
494
|
+
'''Get the ECEF converter's ellipsoid (L{Ellipsoid}).
|
|
495
|
+
'''
|
|
496
|
+
return self.ecef.datum.ellipsoid
|
|
497
|
+
|
|
498
|
+
def forward(self, latlonh, lon=None, height=0, M=False, name=NN):
|
|
499
|
+
'''Convert I{geodetic} C{(lat, lon, height)} to I{local} cartesian
|
|
500
|
+
C{(x, y, z)}.
|
|
501
|
+
|
|
502
|
+
@arg latlonh: Either a C{LatLon}, L{Ltp}, L{Ecef9Tuple} or C{scalar}
|
|
503
|
+
(geodetic) latitude (C{degrees}).
|
|
504
|
+
@kwarg lon: Optional C{scalar} (geodetic) longitude for C{scalar}
|
|
505
|
+
B{C{latlonh}} (C{degrees}).
|
|
506
|
+
@kwarg height: Optional height (C{meter}, conventionally) perpendicular
|
|
507
|
+
to and above (or below) the ellipsoid's surface.
|
|
508
|
+
@kwarg M: Optionally, return the I{concatenated} rotation L{EcefMatrix},
|
|
509
|
+
iff available (C{bool}).
|
|
510
|
+
@kwarg name: Optional name (C{str}).
|
|
511
|
+
|
|
512
|
+
@return: A L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp, ecef, M)}
|
|
513
|
+
with I{local} C{x}, C{y}, C{z}, I{geodetic} C{(lat}, C{lon},
|
|
514
|
+
C{height}, this C{ltp}, C{ecef} (L{Ecef9Tuple}) with
|
|
515
|
+
I{geocentric} C{x}, C{y}, C{z} (and I{geodetic} C{lat},
|
|
516
|
+
C{lon}, C{height}) and the I{concatenated} rotation matrix
|
|
517
|
+
C{M} (L{EcefMatrix}) if requested.
|
|
518
|
+
|
|
519
|
+
@raise LocalError: If B{C{latlonh}} not C{scalar}, C{LatLon}, L{Ltp},
|
|
520
|
+
L{Ecef9Tuple} or invalid or if B{C{lon}} not
|
|
521
|
+
C{scalar} for C{scalar} B{C{latlonh}} or invalid
|
|
522
|
+
or if B{C{height}} invalid.
|
|
523
|
+
'''
|
|
524
|
+
lat, lon, h, n = _llhn4(latlonh, lon, height, Error=LocalError, name=name)
|
|
525
|
+
t = self.ecef._forward(lat, lon, h, n, M=M)
|
|
526
|
+
x, y, z = self.M.rotate(t.xyz, *self._t0_xyz)
|
|
527
|
+
m = self.M.multiply(t.M) if M else None
|
|
528
|
+
return self._9Tuple(x, y, z, lat, lon, h, self, t, m, name=n or self.name)
|
|
529
|
+
|
|
530
|
+
@Property_RO
|
|
531
|
+
def height0(self):
|
|
532
|
+
'''Get the origin's height (C{meter}).
|
|
533
|
+
'''
|
|
534
|
+
return self._t0.height
|
|
535
|
+
|
|
536
|
+
@Property_RO
|
|
537
|
+
def lat0(self):
|
|
538
|
+
'''Get the origin's latitude (C{degrees}).
|
|
539
|
+
'''
|
|
540
|
+
return self._t0.lat
|
|
541
|
+
|
|
542
|
+
@Property_RO
|
|
543
|
+
def latlonheight0(self):
|
|
544
|
+
'''Get the origin's lat-, longitude and height (L{LatLon3Tuple}C{(lat, lon, height)}).
|
|
545
|
+
'''
|
|
546
|
+
return LatLon3Tuple(self.lat0, self.lon0, self.height0, name=self.name)
|
|
547
|
+
|
|
548
|
+
def _local2ecef(self, local, nine=False, M=False):
|
|
549
|
+
'''(INTERNAL) Convert I{local} to geocentric/geodetic, like I{.reverse}.
|
|
550
|
+
|
|
551
|
+
@arg local: Local (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer} or L{Local9Tuple}).
|
|
552
|
+
@kwarg nine: Return 3- or 9-tuple (C{bool}).
|
|
553
|
+
@kwarg M: Include the rotation matrix (C{bool}).
|
|
554
|
+
|
|
555
|
+
@return: A I{geocentric} 3-tuple C{(x, y, z)} or if C{B{nine}=True},
|
|
556
|
+
an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)},
|
|
557
|
+
optionally including rotation matrix C{M} or C{None}.
|
|
558
|
+
'''
|
|
559
|
+
t = self.M.unrotate(local.xyz, *self._t0_xyz)
|
|
560
|
+
if nine:
|
|
561
|
+
t = self.ecef.reverse(*t, M=M)
|
|
562
|
+
return t
|
|
563
|
+
|
|
564
|
+
@Property_RO
|
|
565
|
+
def lon0(self):
|
|
566
|
+
'''Get the origin's longitude (C{degrees}).
|
|
567
|
+
'''
|
|
568
|
+
return self._t0.lon
|
|
569
|
+
|
|
570
|
+
@Property
|
|
571
|
+
def lon00(self):
|
|
572
|
+
'''Get the arbitrary, I{polar} longitude (C{degrees}).
|
|
573
|
+
'''
|
|
574
|
+
return self._lon00
|
|
575
|
+
|
|
576
|
+
@lon00.setter # PYCHOK setter!
|
|
577
|
+
def lon00(self, lon00):
|
|
578
|
+
'''Set the arbitrary, I{polar} longitude (C{degrees}).
|
|
579
|
+
'''
|
|
580
|
+
# lon00 <https://GitHub.com/mrJean1/PyGeodesy/issues/77>
|
|
581
|
+
self._lon00 = Degrees(lon00=lon00)
|
|
582
|
+
|
|
583
|
+
@Property_RO
|
|
584
|
+
def M(self):
|
|
585
|
+
'''Get the rotation matrix (C{EcefMatrix}).
|
|
586
|
+
'''
|
|
587
|
+
return self._t0.M
|
|
588
|
+
|
|
589
|
+
def reset(self, latlonh0=INT0, lon0=INT0, height0=INT0, ecef=None, name=NN, **lon00):
|
|
590
|
+
'''Reset this converter, see L{LocalCartesian.__init__} and L{Ltp.__init__} for more details.
|
|
591
|
+
'''
|
|
592
|
+
if isinstance(latlonh0, LocalCartesian):
|
|
593
|
+
if self._t0:
|
|
594
|
+
_update_all(self)
|
|
595
|
+
self._ecef = latlonh0.ecef
|
|
596
|
+
self._lon00 = latlonh0.lon00
|
|
597
|
+
self._t0 = latlonh0._t0
|
|
598
|
+
n = name or latlonh0.name
|
|
599
|
+
else:
|
|
600
|
+
lat0, lon0, height0, n = _llhn4(latlonh0, lon0, height0, suffix=_0_,
|
|
601
|
+
Error=LocalError, name=name or self.name)
|
|
602
|
+
if ecef: # PYCHOK no cover
|
|
603
|
+
_xinstanceof(self._Ecef, ecef=ecef)
|
|
604
|
+
_update_all(self)
|
|
605
|
+
self._ecef = ecef
|
|
606
|
+
elif self._t0:
|
|
607
|
+
_update_all(self)
|
|
608
|
+
self._t0 = self.ecef._forward(lat0, lon0, height0, n, M=True)
|
|
609
|
+
self.lon00 = _xattr(latlonh0, lon00=_xkwds_get(lon00, lon00=lon0))
|
|
610
|
+
if n:
|
|
611
|
+
self.rename(n)
|
|
612
|
+
|
|
613
|
+
def reverse(self, xyz, y=None, z=None, M=False, name=NN, **lon00):
|
|
614
|
+
'''Convert I{local} C{(x, y, z)} to I{geodetic} C{(lat, lon, height)}.
|
|
615
|
+
|
|
616
|
+
@arg xyz: A I{local} (L{XyzLocal}, L{Enu}, L{Ned}, L{Aer}, L{Local9Tuple}) or
|
|
617
|
+
local C{x} coordinate (C{scalar}).
|
|
618
|
+
@kwarg y: Local C{y} coordinate for C{scalar} B{C{xyz}} and B{C{z}} (C{meter}).
|
|
619
|
+
@kwarg z: Local C{z} coordinate for C{scalar} B{C{xyz}} and B{C{y}} (C{meter}).
|
|
620
|
+
@kwarg M: Optionally, return the I{concatenated} rotation L{EcefMatrix}, iff
|
|
621
|
+
available (C{bool}).
|
|
622
|
+
@kwarg name: Optional name (C{str}).
|
|
623
|
+
@kwarg lon00: An arbitrary, I{polar} longitude (C{degrees}), returned for local
|
|
624
|
+
C{B{x}=0} and C{B{y}=0} at I{polar} latitudes C{abs(B{lat0}) == 90},
|
|
625
|
+
overriding property C{lon00} and default C{B{lon00}=B{lon0}}.
|
|
626
|
+
|
|
627
|
+
@return: An L{Local9Tuple}C{(x, y, z, lat, lon, height, ltp, ecef, M)} with
|
|
628
|
+
I{local} C{x}, C{y}, C{z}, I{geodetic} C{lat}, C{lon}, C{height},
|
|
629
|
+
this C{ltp}, an C{ecef} (L{Ecef9Tuple}) with the I{geocentric} C{x},
|
|
630
|
+
C{y}, C{z} (and I{geodetic} C{lat}, C{lon}, C{height}) and the
|
|
631
|
+
I{concatenated} rotation matrix C{M} (L{EcefMatrix}) if requested.
|
|
632
|
+
|
|
633
|
+
@raise LocalError: Invalid B{C{xyz}} or C{scalar} C{x} or B{C{y}} and/or B{C{z}}
|
|
634
|
+
not C{scalar} for C{scalar} B{C{xyz}}.
|
|
635
|
+
'''
|
|
636
|
+
x, y, z, n = _xyzn4(xyz, y, z, _XyzLocals5, Error=LocalError, name=name)
|
|
637
|
+
c = self.M.unrotate((x, y, z), *self._t0_xyz)
|
|
638
|
+
t = self.ecef.reverse(*c, M=M, lon00=_xkwds_get(lon00, lon00=self.lon00))
|
|
639
|
+
m = self.M.multiply(t.M) if M else None
|
|
640
|
+
return self._9Tuple(x, y, z, t.lat, t.lon, t.height, self, t, m, name=n or self.name)
|
|
641
|
+
|
|
642
|
+
@Property_RO
|
|
643
|
+
def _t0_xyz(self):
|
|
644
|
+
'''(INTERNAL) Get C{(x0, y0, z0)} as L{Vector3Tuple}.
|
|
645
|
+
'''
|
|
646
|
+
return self._t0.xyz
|
|
647
|
+
|
|
648
|
+
def toStr(self, prec=9, **unused): # PYCHOK signature
|
|
649
|
+
'''Return this L{LocalCartesian} as a string.
|
|
650
|
+
|
|
651
|
+
@kwarg prec: Precision, number of (decimal) digits (0..9).
|
|
652
|
+
|
|
653
|
+
@return: This L{LocalCartesian} representation (C{str}).
|
|
654
|
+
'''
|
|
655
|
+
return self.attrs(_lat0_, _lon0_, _height0_, _M_, _ecef_, _name_, prec=prec)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
class Ltp(LocalCartesian):
|
|
659
|
+
'''A I{local tangent plan} (LTP), a sub-class of C{LocalCartesian} with
|
|
660
|
+
(re-)configurable ECEF converter.
|
|
661
|
+
'''
|
|
662
|
+
_Ecef = _EcefBase
|
|
663
|
+
|
|
664
|
+
def __init__(self, latlonh0=INT0, lon0=INT0, height0=INT0, ecef=None, name=NN, **lon00):
|
|
665
|
+
'''New C{Ltp}, see L{LocalCartesian.__init__} for more details.
|
|
666
|
+
|
|
667
|
+
@kwarg ecef: Optional ECEF converter (L{EcefKarney}, L{EcefFarrell21},
|
|
668
|
+
L{EcefFarrell22}, L{EcefSudano}, L{EcefVeness} or
|
|
669
|
+
L{EcefYou} I{instance}), overriding the default
|
|
670
|
+
L{EcefKarney}C{(datum=Datums.WGS84)} for C{scalar}.
|
|
671
|
+
|
|
672
|
+
@raise TypeError: Invalid B{C{ecef}}.
|
|
673
|
+
'''
|
|
674
|
+
LocalCartesian.reset(self, latlonh0, lon0=lon0, height0=height0,
|
|
675
|
+
ecef=ecef, name=name, **lon00)
|
|
676
|
+
|
|
677
|
+
@Property
|
|
678
|
+
def ecef(self):
|
|
679
|
+
'''Get this LTP's ECEF converter (C{Ecef...} I{instance}).
|
|
680
|
+
'''
|
|
681
|
+
return self._ecef
|
|
682
|
+
|
|
683
|
+
@ecef.setter # PYCHOK setter!
|
|
684
|
+
def ecef(self, ecef):
|
|
685
|
+
'''Set this LTP's ECEF converter (C{Ecef...} I{instance}).
|
|
686
|
+
|
|
687
|
+
@raise TypeError: Invalid B{C{ecef}}.
|
|
688
|
+
'''
|
|
689
|
+
_xinstanceof(_EcefBase, ecef=ecef)
|
|
690
|
+
if self._ecef != ecef: # PYCHOK no cover
|
|
691
|
+
self.reset(self._t0)
|
|
692
|
+
self._ecef = ecef
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
class _ChLV(object):
|
|
696
|
+
'''(INTERNAL) Base class for C{ChLV*} classes.
|
|
697
|
+
'''
|
|
698
|
+
_03_falsing = ChLVyx2Tuple(0.6e6, 0.2e6)
|
|
699
|
+
# _92_falsing = ChLVYX2Tuple(2.0e6, 1.0e6) # _95_ - _03_
|
|
700
|
+
_95_falsing = ChLVEN2Tuple(2.6e6, 1.2e6)
|
|
701
|
+
|
|
702
|
+
def _ChLV9Tuple(self, fw, M, name, *Y_X_h_lat_lon_h):
|
|
703
|
+
'''(INTERNAL) Helper for C{ChLVa/e.forward} and C{.reverse}.
|
|
704
|
+
'''
|
|
705
|
+
if bool(M): # PYCHOK no cover
|
|
706
|
+
m = self.forward if fw else self.reverse # PYCHOK attr
|
|
707
|
+
n = _DOT_(self.__class__.__name__, m.__name__)
|
|
708
|
+
raise _NotImplementedError(unstr(n, M=M), txt=None)
|
|
709
|
+
t = Y_X_h_lat_lon_h + (self, self._t0, None) # PYCHOK _t0
|
|
710
|
+
return ChLV9Tuple(t, name=name)
|
|
711
|
+
|
|
712
|
+
@property_RO
|
|
713
|
+
def _enh_n_h(self):
|
|
714
|
+
'''(INTERNAL) Get C{ChLV*.reverse} args[1:4] names, I{once}.
|
|
715
|
+
'''
|
|
716
|
+
_ChLV._enh_n_h = t = _args_kwds_names(_ChLV.reverse)[1:4] # overwrite property_RO
|
|
717
|
+
# assert _args_kwds_names( ChLV.reverse)[1:4] == t
|
|
718
|
+
# assert _args_kwds_names(ChLVa.reverse)[1:4] == t
|
|
719
|
+
# assert _args_kwds_names(ChLVe.reverse)[1:4] == t
|
|
720
|
+
return t
|
|
721
|
+
|
|
722
|
+
def forward(self, latlonh, lon=None, height=0, M=None, name=NN): # PYCHOK no cover
|
|
723
|
+
'''Convert WGS84 geodetic to I{Swiss} projection coordinates. I{Must be overloaded}.
|
|
724
|
+
|
|
725
|
+
@arg latlonh: Either a C{LatLon}, L{Ltp} or C{scalar} (geodetic) latitude (C{degrees}).
|
|
726
|
+
@kwarg lon: Optional, C{scalar} (geodetic) longitude for C{scalar} B{C{latlonh}} (C{degrees}).
|
|
727
|
+
@kwarg height: Optional, height, vertically above (or below) the surface of the ellipsoid
|
|
728
|
+
(C{meter}) for C{scalar} B{C{latlonh}} and B{C{lon}}.
|
|
729
|
+
@kwarg M: If C{True}, return the I{concatenated} rotation L{EcefMatrix} iff available
|
|
730
|
+
for C{ChLV} only, C{None} otherwise (C{bool}).
|
|
731
|
+
@kwarg name: Optional name (C{str}).
|
|
732
|
+
|
|
733
|
+
@return: A L{ChLV9Tuple}C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with the unfalsed
|
|
734
|
+
I{Swiss Y, X} coordinates, I{Swiss h_} height, the given I{geodetic} C{lat},
|
|
735
|
+
C{lon} and C{height}, this C{ChLV*} instance and C{ecef} (L{Ecef9Tuple}) at
|
|
736
|
+
I{Bern, Ch} and rotation matrix C{M}. The returned C{ltp} is this C{ChLV},
|
|
737
|
+
C{ChLVa} or C{ChLVe} instance.
|
|
738
|
+
|
|
739
|
+
@raise LocalError: Invalid or non-C{scalar} B{C{latlonh}}, B{C{lon}} or B{C{height}}.
|
|
740
|
+
'''
|
|
741
|
+
notOverloaded(self, latlonh, lon=lon, height=height, M=M, name=name)
|
|
742
|
+
|
|
743
|
+
def reverse(self, enh_, n=None, h_=0, M=None, **name): # PYCHOK no cover
|
|
744
|
+
'''Convert I{Swiss} projection to WGS84 geodetic coordinates.
|
|
745
|
+
|
|
746
|
+
@arg enh_: A Swiss projection (L{ChLV9Tuple}) or the C{scalar}, falsed I{Swiss E_LV95}
|
|
747
|
+
or I{y_LV03} easting (C{meter}).
|
|
748
|
+
@kwarg n: Falsed I{Swiss N_LV85} or I{x_LV03} northing for C{scalar} B{C{enh_}} and
|
|
749
|
+
B{C{h_}} (C{meter}).
|
|
750
|
+
@kwarg h_: I{Swiss h'} height for C{scalar} B{C{enh_}} and B{C{n}} (C{meter}).
|
|
751
|
+
@kwarg M: If C{True}, return the I{concatenated} rotation L{EcefMatrix} iff available
|
|
752
|
+
for C{ChLV} only, C{None} otherwise (C{bool}).
|
|
753
|
+
@kwarg name: Optional name (C{str}).
|
|
754
|
+
|
|
755
|
+
@return: A L{ChLV9Tuple}C{(Y, X, h_, lat, lon, height, ltp, ecef, M)} with the unfalsed
|
|
756
|
+
I{Swiss Y, X} coordinates, I{Swiss h_} height, the given I{geodetic} C{lat},
|
|
757
|
+
C{lon} and C{height}, this C{ChLV*} instance and C{ecef} (L{Ecef9Tuple}) at
|
|
758
|
+
I{Bern, Ch} and rotation matrix C{M}. The returned C{ltp} is this C{ChLV},
|
|
759
|
+
C{ChLVa} or C{ChLVe} instance.
|
|
760
|
+
|
|
761
|
+
@raise LocalError: Invalid or non-C{scalar} B{C{enh_}}, B{C{n}} or B{C{h_}}.
|
|
762
|
+
'''
|
|
763
|
+
notOverloaded(self, enh_, n=n, h_=h_, M=M, **name)
|
|
764
|
+
|
|
765
|
+
@staticmethod
|
|
766
|
+
def _falsing2(LV95):
|
|
767
|
+
'''(INTERNAL) Get the C{LV95} or C{LV03} falsing.
|
|
768
|
+
'''
|
|
769
|
+
return _ChLV._95_falsing if LV95 in (True, 95) else (
|
|
770
|
+
_ChLV._03_falsing if LV95 in (False, 3) else ChLVYX2Tuple(0, 0))
|
|
771
|
+
|
|
772
|
+
@staticmethod
|
|
773
|
+
def _llh2abh_3(lat, lon, h):
|
|
774
|
+
'''(INTERNAL) Helper for C{ChLVa/e.forward}.
|
|
775
|
+
'''
|
|
776
|
+
def _deg2ab(deg, sLL):
|
|
777
|
+
# convert degrees to arc-seconds
|
|
778
|
+
def _dms(ds, p, q, swap):
|
|
779
|
+
d = _floor(ds)
|
|
780
|
+
t = (ds - d) * p
|
|
781
|
+
m = _floor(t)
|
|
782
|
+
s = (t - m) * p
|
|
783
|
+
if swap:
|
|
784
|
+
d, s = s, d
|
|
785
|
+
return d + (m + s * q) * q
|
|
786
|
+
|
|
787
|
+
s = _dms(deg, _60_0, _0_01, False) # deg2sexag
|
|
788
|
+
s = _dms( s, _100_0, _60_0, True) # sexag2asec
|
|
789
|
+
return (s - sLL) / ChLV._s_ab
|
|
790
|
+
|
|
791
|
+
a = _deg2ab(lat, ChLV._sLat) # phi', lat_aux
|
|
792
|
+
b = _deg2ab(lon, ChLV._sLon) # lam', lng_aux
|
|
793
|
+
h_ = fsumf_(h, -ChLV.Bern.height, 2.73 * b, 6.94 * a)
|
|
794
|
+
return a, b, h_
|
|
795
|
+
|
|
796
|
+
@staticmethod
|
|
797
|
+
def _YXh_2abh3(Y, X, h_):
|
|
798
|
+
'''(INTERNAL) Helper for C{ChLVa/e.reverse}.
|
|
799
|
+
'''
|
|
800
|
+
def _YX2ab(YX):
|
|
801
|
+
return YX * ChLV._ab_m
|
|
802
|
+
|
|
803
|
+
a, b = map1(_YX2ab, Y, X)
|
|
804
|
+
h = fsumf_(h_, ChLV.Bern.height, -12.6 * a, -22.64 * b)
|
|
805
|
+
return a, b, h
|
|
806
|
+
|
|
807
|
+
def _YXh_n4(self, enh_, n, h_, **name):
|
|
808
|
+
'''(INTERNAL) Helper for C{ChLV*.reverse}.
|
|
809
|
+
'''
|
|
810
|
+
Y, X, h_, name = _xyzn4(enh_, n, h_, ChLV9Tuple,
|
|
811
|
+
_xyz_y_z_names=self._enh_n_h, **name)
|
|
812
|
+
if isinstance(enh_, ChLV9Tuple):
|
|
813
|
+
Y, X = enh_.Y, enh_.X
|
|
814
|
+
else: # isscalar(enh_)
|
|
815
|
+
Y, X = ChLV.unfalse2(Y, X) # PYCHOK ChLVYX2Tuple
|
|
816
|
+
return Y, X, h_, name
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
class ChLV(_ChLV, Ltp):
|
|
820
|
+
'''Conversion between I{WGS84 geodetic} and I{Swiss} projection coordinates using
|
|
821
|
+
L{pygeodesy.EcefKarney}'s Earth-Centered, Earth-Fixed (ECEF) methods.
|
|
822
|
+
|
|
823
|
+
@see: U{Swiss projection formulas<https://www.SwissTopo.admin.CH/en/maps-data-online/
|
|
824
|
+
calculation-services.html>}, page 7ff, U{NAVREF<https://www.SwissTopo.admin.CH/en/
|
|
825
|
+
maps-data-online/calculation-services/navref.html>}, U{REFRAME<https://www.SwissTopo.admin.CH/
|
|
826
|
+
en/maps-data-online/calculation-services/reframe.html>} and U{SwissTopo Scripts GPS WGS84
|
|
827
|
+
<-> LV03<https://GitHub.com/ValentinMinder/Swisstopo-WGS84-LV03>}.
|
|
828
|
+
'''
|
|
829
|
+
_9Tuple = ChLV9Tuple
|
|
830
|
+
|
|
831
|
+
_ab_d = 0.36 # a, b units per degree, ...
|
|
832
|
+
_ab_m = 1.0e-6 # ... per meter and ...
|
|
833
|
+
_ab_M = _1_0 # ... per 1,000 Km or 1 Mm
|
|
834
|
+
_s_d = _3600_0 # arc-seconds per degree ...
|
|
835
|
+
_s_ab = _s_d / _ab_d # ... and per a, b unit
|
|
836
|
+
_sLat = 169028.66 # Bern, Ch in ...
|
|
837
|
+
_sLon = 26782.5 # ... arc-seconds ...
|
|
838
|
+
# lat, lon, height == 46°57'08.66", 7°26'22.50", 49.55m ("new" 46°57'07.89", 7°26'22.335")
|
|
839
|
+
Bern = LatLon4Tuple(_sLat / _s_d, _sLon / _s_d, 49.55, _WGS84, name='Bern')
|
|
840
|
+
|
|
841
|
+
def __init__(self, latlonh0=Bern, **other_Ltp_kwds):
|
|
842
|
+
'''New ECEF-based I{WGS84-Swiss} L{ChLV} converter, centered at I{Bern, Ch}.
|
|
843
|
+
|
|
844
|
+
@kwarg latlonh0: The I{geodetic} origin and height, overriding C{Bern, Ch}.
|
|
845
|
+
@kwarg other_Ltp_kwds: Optional, other L{Ltp.__init__} keyword arguments.
|
|
846
|
+
|
|
847
|
+
@see: L{Ltp.__init__} for more information.
|
|
848
|
+
'''
|
|
849
|
+
Ltp.__init__(self, latlonh0, **_xkwds(other_Ltp_kwds, ecef=None, name=ChLV.Bern.name))
|
|
850
|
+
|
|
851
|
+
def forward(self, latlonh, lon=None, height=0, M=None, name=NN): # PYCHOK unused M
|
|
852
|
+
# overloaded for the _ChLV.forward.__doc__
|
|
853
|
+
return Ltp.forward(self, latlonh, lon=lon, height=height, M=M, name=name)
|
|
854
|
+
|
|
855
|
+
def reverse(self, enh_, n=None, h_=0, M=None, **name): # PYCHOK signature
|
|
856
|
+
# overloaded for the _ChLV.reverse.__doc__
|
|
857
|
+
Y, X, h_, name = self._YXh_n4(enh_, n, h_, **name)
|
|
858
|
+
return Ltp.reverse(self, Y, X, h_, M=M, name=name)
|
|
859
|
+
|
|
860
|
+
@staticmethod
|
|
861
|
+
def false2(Y, X, LV95=True, name=NN):
|
|
862
|
+
'''Add the I{Swiss LV95} or I{LV03} falsing.
|
|
863
|
+
|
|
864
|
+
@arg Y: Unfalsed I{Swiss Y} easting (C{meter}).
|
|
865
|
+
@arg X: Unfalsed I{Swiss X} northing (C{meter}).
|
|
866
|
+
@kwarg LV95: If C{True} add C{LV95} falsing, if C{False} add
|
|
867
|
+
C{LV03} falsing, otherwise leave unfalsed.
|
|
868
|
+
@kwarg name: Optional name (C{str}).
|
|
869
|
+
|
|
870
|
+
@return: A L{ChLVEN2Tuple}C{(E_LV95, N_LV95)} or a
|
|
871
|
+
L{ChLVyx2Tuple}C{(y_LV03, x_LV03)} with falsed B{C{Y}}
|
|
872
|
+
and B{C{X}}, otherwise a L{ChLVYX2Tuple}C{(Y, X)}
|
|
873
|
+
with B{C{Y}} and B{C{X}} as-is.
|
|
874
|
+
'''
|
|
875
|
+
e, n = t = _ChLV._falsing2(LV95)
|
|
876
|
+
return t.classof(e + Y, n + X, name=name)
|
|
877
|
+
|
|
878
|
+
@staticmethod
|
|
879
|
+
def isLV03(e, n):
|
|
880
|
+
'''Is C{(B{e}, B{n})} a valid I{Swiss LV03} projection?
|
|
881
|
+
|
|
882
|
+
@arg e: Falsed (or unfalsed) I{Swiss} easting (C{meter}).
|
|
883
|
+
@arg n: Falsed (or unfalsed) I{Swiss} northing (C{meter}).
|
|
884
|
+
|
|
885
|
+
@return: C{True} if C{(B{e}, B{n})} is a valid, falsed I{Swiss
|
|
886
|
+
LV03}, projection C{False} otherwise.
|
|
887
|
+
'''
|
|
888
|
+
# @see: U{Map<https://www.SwissTopo.admin.CH/en/knowledge-facts/
|
|
889
|
+
# surveying-geodesy/reference-frames/local/lv95.html>}
|
|
890
|
+
return 400.0e3 < e < 900.0e3 and 40.0e3 < n < 400.0e3
|
|
891
|
+
|
|
892
|
+
@staticmethod
|
|
893
|
+
def isLV95(e, n, raiser=True):
|
|
894
|
+
'''Is C{(B{e}, B{n})} a valid I{Swiss LV95} or I{LV03} projection?
|
|
895
|
+
|
|
896
|
+
@arg e: Falsed (or unfalsed) I{Swiss} easting (C{meter}).
|
|
897
|
+
@arg n: Falsed (or unfalsed) I{Swiss} northing (C{meter}).
|
|
898
|
+
@kwarg raiser: If C{True}, throw a L{LocalError} if B{C{e}} and
|
|
899
|
+
B{C{n}} are invalid I{Swiss LV95} nor I{LV03}.
|
|
900
|
+
|
|
901
|
+
@return: C{True} or C{False} if C{(B{e}, B{n})} is a valid I{Swiss
|
|
902
|
+
LV95} respectively I{LV03} projection, C{None} otherwise.
|
|
903
|
+
'''
|
|
904
|
+
if ChLV.isLV03(e, n):
|
|
905
|
+
return False
|
|
906
|
+
elif ChLV.isLV03(e - 2.0e6, n - 1.0e6): # _92_falsing = _95_ - _03_
|
|
907
|
+
return True
|
|
908
|
+
elif raiser: # PYCHOK no cover
|
|
909
|
+
raise LocalError(unstr(ChLV.isLV95, e=e, n=n))
|
|
910
|
+
return None
|
|
911
|
+
|
|
912
|
+
@staticmethod
|
|
913
|
+
def unfalse2(e, n, LV95=None, name=NN):
|
|
914
|
+
'''Remove the I{Swiss LV95} or I{LV03} falsing.
|
|
915
|
+
|
|
916
|
+
@arg e: Falsed I{Swiss E_LV95} or I{y_LV03} easting (C{meter}).
|
|
917
|
+
@arg n: Falsed I{Swiss N_LV95} or I{x_LV03} northing (C{meter}).
|
|
918
|
+
@kwarg LV95: If C{True} remove I{LV95} falsing, if C{False} remove
|
|
919
|
+
I{LV03} falsing, otherwise use method C{isLV95(B{e},
|
|
920
|
+
B{n})}.
|
|
921
|
+
@kwarg name: Optional name (C{str}).
|
|
922
|
+
|
|
923
|
+
@return: A L{ChLVYX2Tuple}C{(Y, X)} with the unfalsed B{C{e}}
|
|
924
|
+
respectively B{C{n}}.
|
|
925
|
+
'''
|
|
926
|
+
Y, X = _ChLV._falsing2(ChLV.isLV95(e, n) if LV95 is None else LV95)
|
|
927
|
+
return ChLVYX2Tuple(e - Y, n - X, name=name)
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
class ChLVa(_ChLV, LocalCartesian):
|
|
931
|
+
'''Conversion between I{WGS84 geodetic} and I{Swiss} projection coordinates
|
|
932
|
+
using the U{Approximate<https://www.SwissTopo.admin.CH/en/maps-data-online/
|
|
933
|
+
calculation-services.html>} formulas, page 13.
|
|
934
|
+
|
|
935
|
+
@see: Older U{references<https://GitHub.com/alphasldiallo/Swisstopo-WGS84-LV03>}.
|
|
936
|
+
'''
|
|
937
|
+
def __init__(self, name=ChLV.Bern.name):
|
|
938
|
+
'''New I{Approximate WGS84-Swiss} L{ChLVa} converter, centered at I{Bern, Ch}.
|
|
939
|
+
|
|
940
|
+
@kwarg name: Optional name (C{str}), overriding C{Bern.name}.
|
|
941
|
+
'''
|
|
942
|
+
LocalCartesian.__init__(self, latlonh0=ChLV.Bern, name=name)
|
|
943
|
+
|
|
944
|
+
def forward(self, latlonh, lon=None, height=0, M=None, name=NN):
|
|
945
|
+
# overloaded for the _ChLV.forward.__doc__
|
|
946
|
+
lat, lon, h, name = _llhn4(latlonh, lon, height, name=name)
|
|
947
|
+
a, b, h_ = _ChLV._llh2abh_3(lat, lon, h)
|
|
948
|
+
a2, b2 = a**2, b**2
|
|
949
|
+
|
|
950
|
+
Y = fsumf_( 72.37, 211455.93 * b,
|
|
951
|
+
-10938.51 * b * a,
|
|
952
|
+
-0.36 * b * a2,
|
|
953
|
+
-44.54 * b * b2) # + 600_000
|
|
954
|
+
X = fsumf_(147.07, 308807.95 * a,
|
|
955
|
+
3745.25 * b2,
|
|
956
|
+
76.63 * a2,
|
|
957
|
+
-194.56 * b2 * a,
|
|
958
|
+
119.79 * a2 * a) # + 200_000
|
|
959
|
+
return self._ChLV9Tuple(True, M, name, Y, X, h_, lat, lon, h)
|
|
960
|
+
|
|
961
|
+
def reverse(self, enh_, n=None, h_=0, M=None, **name): # PYCHOK signature
|
|
962
|
+
# overloaded for the _ChLV.reverse.__doc__
|
|
963
|
+
Y, X, h_, name = self._YXh_n4(enh_, n, h_, **name)
|
|
964
|
+
a, b, h = _ChLV._YXh_2abh3(Y, X, h_)
|
|
965
|
+
ab_d, a2, b2 = ChLV._ab_d, a**2, b**2
|
|
966
|
+
|
|
967
|
+
lat = Fsum(16.9023892, 3.238272 * b,
|
|
968
|
+
-0.270978 * a2,
|
|
969
|
+
-0.002528 * b2,
|
|
970
|
+
-0.0447 * a2 * b,
|
|
971
|
+
-0.014 * b2 * b).fover(ab_d)
|
|
972
|
+
lon = Fsum( 2.6779094, 4.728982 * a,
|
|
973
|
+
0.791484 * a * b,
|
|
974
|
+
0.1306 * a * b2,
|
|
975
|
+
-0.0436 * a * a2).fover(ab_d)
|
|
976
|
+
return self._ChLV9Tuple(False, M, name, Y, X, h_, lat, lon, h)
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
class ChLVe(_ChLV, LocalCartesian):
|
|
980
|
+
'''Conversion between I{WGS84 geodetic} and I{Swiss} projection coordinates
|
|
981
|
+
using the U{Ellipsoidal approximate<https://www.SwissTopo.admin.CH/en/
|
|
982
|
+
maps-data-online/calculation-services.html>} formulas, pp 10-11 and U{Bolliger,
|
|
983
|
+
J.<https://eMuseum.GGGS.CH/literatur-lv/liste-Dateien/1967_Bolliger_a.pdf>}
|
|
984
|
+
pp 148-151 (also U{GGGS<https://eMuseum.GGGS.CH/literatur-lv/liste.htm>}).
|
|
985
|
+
|
|
986
|
+
@note: Methods L{ChLVe.forward} and L{ChLVe.reverse} have an additional keyword
|
|
987
|
+
argument C{B{gamma}=False} to approximate the I{meridian convergence}.
|
|
988
|
+
If C{B{gamma}=True} a 2-tuple C{(t, gamma)} is returned with C{t} the
|
|
989
|
+
usual result (C{ChLV9Tuple}) and C{gamma}, the I{meridian convergence}
|
|
990
|
+
(decimal C{degrees}). To convert C{gamma} to C{grades} or C{gons},
|
|
991
|
+
use function L{pygeodesy.degrees2grades}.
|
|
992
|
+
|
|
993
|
+
@see: Older U{references<https://GitHub.com/alphasldiallo/Swisstopo-WGS84-LV03>}.
|
|
994
|
+
'''
|
|
995
|
+
def __init__(self, name=ChLV.Bern.name):
|
|
996
|
+
'''New I{Approximate WGS84-Swiss} L{ChLVe} converter, centered at I{Bern, Ch}.
|
|
997
|
+
|
|
998
|
+
@kwarg name: Optional name (C{str}), overriding C{Bern.name}.
|
|
999
|
+
'''
|
|
1000
|
+
LocalCartesian.__init__(self, latlonh0=ChLV.Bern, name=name)
|
|
1001
|
+
|
|
1002
|
+
def forward(self, latlonh, lon=None, height=0, M=None, name=NN, gamma=False): # PYCHOK gamma
|
|
1003
|
+
# overloaded for the _ChLV.forward.__doc__
|
|
1004
|
+
lat, lon, h, name = _llhn4(latlonh, lon, height, name=name)
|
|
1005
|
+
a, b, h_ = _ChLV._llh2abh_3(lat, lon, h)
|
|
1006
|
+
ab_M, z, _H = ChLV._ab_M, 0, Fhorner
|
|
1007
|
+
|
|
1008
|
+
B1 = _H(a, 211428.533991, -10939.608605, -2.658213, -8.539078, -0.00345, -0.007992)
|
|
1009
|
+
B3 = _H(a, -44.232717, 4.291740, -0.309883, 0.013924)
|
|
1010
|
+
B5 = _H(a, 0.019784, -0.004277)
|
|
1011
|
+
Y = _H(b, z, B1, z, B3, z, B5).fover(ab_M) # 1,000 Km!
|
|
1012
|
+
|
|
1013
|
+
B0 = _H(a, z, 308770.746371, 75.028131, 120.435227, 0.009488, 0.070332, -0.00001)
|
|
1014
|
+
B2 = _H(a, 3745.408911, -193.792705, 4.340858, -0.376174, 0.004053)
|
|
1015
|
+
B4 = _H(a, -0.734684, 0.144466, -0.011842)
|
|
1016
|
+
B6 = 0.000488
|
|
1017
|
+
X = _H(b, B0, z, B2, z, B4, z, B6).fover(ab_M) # 1,000 Km!
|
|
1018
|
+
|
|
1019
|
+
t = self._ChLV9Tuple(True, M, name, Y, X, h_, lat, lon, h)
|
|
1020
|
+
if gamma:
|
|
1021
|
+
U1 = _H(a, 2255515.207166, 2642.456961, 1.284180, 2.577486, 0.001165)
|
|
1022
|
+
U3 = _H(a, -412.991934, 64.106344, -2.679566, 0.123833)
|
|
1023
|
+
U5 = _H(a, 0.204129, -0.037725)
|
|
1024
|
+
g = _H(b, z, U1, z, U3, z, U5).fover(ChLV._ab_m) # * ChLV._ab_d degrees?
|
|
1025
|
+
t = t, g
|
|
1026
|
+
return t
|
|
1027
|
+
|
|
1028
|
+
def reverse(self, enh_, n=None, h_=0, M=None, name=NN, gamma=False): # PYCHOK gamma
|
|
1029
|
+
# overloaded for the _ChLV.reverse.__doc__
|
|
1030
|
+
Y, X, h_, name = self._YXh_n4(enh_, n, h_, name=name)
|
|
1031
|
+
a, b, h = _ChLV._YXh_2abh3(Y, X, h_)
|
|
1032
|
+
s_d, _H, z = ChLV._s_d, Fhorner, 0
|
|
1033
|
+
|
|
1034
|
+
A0 = _H(b, ChLV._sLat, 32386.4877666, -25.486822, -132.457771, 0.48747, 0.81305, -0.0069)
|
|
1035
|
+
A2 = _H(b, -2713.537919, -450.442705, -75.53194, -14.63049, -2.7604)
|
|
1036
|
+
A4 = _H(b, 24.42786, 13.20703, 4.7476)
|
|
1037
|
+
A6 = -0.4249
|
|
1038
|
+
lat = _H(a, A0, z, A2, z, A4, z, A6).fover(s_d)
|
|
1039
|
+
|
|
1040
|
+
A1 = _H(b, 47297.3056722, 7925.714783, 1328.129667, 255.02202, 48.17474, 9.0243)
|
|
1041
|
+
A3 = _H(b, -442.709889, -255.02202, -96.34947, -30.0808)
|
|
1042
|
+
A5 = _H(b, 9.63495, 9.0243)
|
|
1043
|
+
lon = _H(a, ChLV._sLon, A1, z, A3, z, A5).fover(s_d)
|
|
1044
|
+
# == (ChLV._sLon + a * (A1 + a**2 * (A3 + a**2 * A5))) / s_d
|
|
1045
|
+
|
|
1046
|
+
t = self._ChLV9Tuple(False, M, name, Y, X, h_, lat, lon, h)
|
|
1047
|
+
if gamma:
|
|
1048
|
+
U1 = _H(b, 106679.792202, 17876.57022, 4306.5241, 794.87772, 148.1545, 27.8725)
|
|
1049
|
+
U3 = _H(b, -1435.508, -794.8777, -296.309, -92.908)
|
|
1050
|
+
U5 = _H(b, 29.631, 27.873)
|
|
1051
|
+
g = _H(a, z, U1, z, U3, z, U5).fover(ChLV._s_ab) # degrees
|
|
1052
|
+
t = t, g
|
|
1053
|
+
return t
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def tyr3d(tilt=INT0, yaw=INT0, roll=INT0, Vector=Vector3d, **Vector_kwds):
|
|
1057
|
+
'''Convert an attitude oriention into a (3-D) direction vector.
|
|
1058
|
+
|
|
1059
|
+
@kwarg tilt: Pitch, elevation from horizontal (C{degrees}), negative down
|
|
1060
|
+
(clockwise rotation along and around the x-axis).
|
|
1061
|
+
@kwarg yaw: Bearing, heading (compass C{degrees360}), clockwise from North
|
|
1062
|
+
(counter-clockwise rotation along and around the z-axis).
|
|
1063
|
+
@kwarg roll: Roll, bank (C{degrees}), positive to the right and down
|
|
1064
|
+
(clockwise rotation along and around the y-axis).
|
|
1065
|
+
|
|
1066
|
+
@return: A named B{C{Vector}} instance or if B{C{Vector}} is C{None},
|
|
1067
|
+
a named L{Vector3Tuple}C{(x, y, z)}.
|
|
1068
|
+
|
|
1069
|
+
@see: U{Yaw, pitch, and roll rotations<http://MSL.CS.UIUC.edu/planning/node102.html>}
|
|
1070
|
+
and function L{pygeodesy.hartzell} argument C{los}.
|
|
1071
|
+
'''
|
|
1072
|
+
d = Attitude4Tuple(_0_0, tilt, yaw, roll).tyr3d
|
|
1073
|
+
return d if Vector is type(d) else (
|
|
1074
|
+
Vector3Tuple(d.x, d.y, d.z, name=d.name) if Vector is None else
|
|
1075
|
+
Vector(d.x, d.y, d.z, **_xkwds(Vector_kwds, name=d.name))) # PYCHOK indent
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
def _xLtp(ltp, *dflt):
|
|
1079
|
+
'''(INTERNAL) Validate B{C{ltp}}.
|
|
1080
|
+
'''
|
|
1081
|
+
if dflt and ltp is None:
|
|
1082
|
+
ltp = dflt[0]
|
|
1083
|
+
if isinstance(ltp, (LocalCartesian, Ltp)):
|
|
1084
|
+
return ltp
|
|
1085
|
+
raise _TypesError(_ltp_, ltp, Ltp, LocalCartesian)
|
|
1086
|
+
|
|
1087
|
+
# **) MIT License
|
|
1088
|
+
#
|
|
1089
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
1090
|
+
#
|
|
1091
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
1092
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
1093
|
+
# to deal in the Software without restriction, including without limitation
|
|
1094
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
1095
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
1096
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
1097
|
+
#
|
|
1098
|
+
# The above copyright notice and this permission notice shall be included
|
|
1099
|
+
# in all copies or substantial portions of the Software.
|
|
1100
|
+
#
|
|
1101
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
1102
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1103
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
1104
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
1105
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
1106
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
1107
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|