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/css.py
ADDED
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Cassini-Soldner (CSS) projection.
|
|
5
|
+
|
|
6
|
+
Classes L{CassiniSoldner}, L{Css} and L{CSSError} requiring I{Charles Karney}'s
|
|
7
|
+
U{geographiclib <https://PyPI.org/project/geographiclib>} Python package to be
|
|
8
|
+
installed.
|
|
9
|
+
'''
|
|
10
|
+
|
|
11
|
+
from pygeodesy.basics import islistuple, neg, _xinstanceof, _xsubclassof
|
|
12
|
+
from pygeodesy.constants import _umod_360, _0_0, _0_5, _90_0
|
|
13
|
+
from pygeodesy.datums import _ellipsoidal_datum, _WGS84
|
|
14
|
+
from pygeodesy.ellipsoidalBase import LatLonEllipsoidalBase as _LLEB
|
|
15
|
+
from pygeodesy.errors import _ValueError, _xdatum, _xellipsoidal, \
|
|
16
|
+
_xattr, _xkwds
|
|
17
|
+
from pygeodesy.interns import NN, _azimuth_, _COMMASPACE_, _easting_, \
|
|
18
|
+
_lat_, _lon_, _m_, _name_, _northing_, \
|
|
19
|
+
_reciprocal_, _SPACE_
|
|
20
|
+
from pygeodesy.interns import _C_ # PYCHOK used!
|
|
21
|
+
from pygeodesy.karney import _atan2d, _copysign, _diff182, _norm2, \
|
|
22
|
+
_norm180, _signBit, _sincos2d, fabs
|
|
23
|
+
from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
|
|
24
|
+
from pygeodesy.named import _NamedBase, _NamedTuple, nameof
|
|
25
|
+
from pygeodesy.namedTuples import EasNor2Tuple, EasNor3Tuple, \
|
|
26
|
+
LatLon2Tuple, LatLon4Tuple, _LL4Tuple
|
|
27
|
+
from pygeodesy.props import deprecated_Property_RO, Property, \
|
|
28
|
+
Property_RO, _update_all
|
|
29
|
+
from pygeodesy.streprs import Fmt, _fstrENH2, _fstrLL0, _xzipairs
|
|
30
|
+
from pygeodesy.units import Bearing, Degrees, Easting, Height, _heigHt, \
|
|
31
|
+
Lat_, Lon_, Northing, Scalar
|
|
32
|
+
|
|
33
|
+
# from math import fabs # from .karney
|
|
34
|
+
|
|
35
|
+
__all__ = _ALL_LAZY.css
|
|
36
|
+
__version__ = '24.02.21'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _CS0(cs0):
|
|
40
|
+
'''(INTERNAL) Get/set default projection.
|
|
41
|
+
'''
|
|
42
|
+
if cs0 is None:
|
|
43
|
+
cs0 = Css._CS0
|
|
44
|
+
if cs0 is None:
|
|
45
|
+
Css._CS0 = cs0 = CassiniSoldner(_0_0, _0_0, name='Default')
|
|
46
|
+
else:
|
|
47
|
+
_xinstanceof(CassiniSoldner, cs0=cs0)
|
|
48
|
+
return cs0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CSSError(_ValueError):
|
|
52
|
+
'''Cassini-Soldner (CSS) conversion or other L{Css} issue.
|
|
53
|
+
'''
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CassiniSoldner(_NamedBase):
|
|
58
|
+
'''Cassini-Soldner projection, a Python version of I{Karney}'s C++ class U{CassiniSoldner
|
|
59
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1CassiniSoldner.html>}.
|
|
60
|
+
'''
|
|
61
|
+
_cb0 = _0_0
|
|
62
|
+
_datum = _WGS84 # L{Datum}
|
|
63
|
+
_geodesic = None
|
|
64
|
+
_latlon0 = ()
|
|
65
|
+
_meridian = None
|
|
66
|
+
_sb0 = _0_0
|
|
67
|
+
|
|
68
|
+
def __init__(self, lat0, lon0, datum=_WGS84, name=NN):
|
|
69
|
+
'''New L{CassiniSoldner} projection.
|
|
70
|
+
|
|
71
|
+
@arg lat0: Latitude of center point (C{degrees90}).
|
|
72
|
+
@arg lon0: Longitude of center point (C{degrees180}).
|
|
73
|
+
@kwarg datum: Optional datum or ellipsoid (L{Datum},
|
|
74
|
+
L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).
|
|
75
|
+
@kwarg name: Optional name (C{str}).
|
|
76
|
+
|
|
77
|
+
@raise CSSError: Invalid B{C{lat}} or B{C{lon}}.
|
|
78
|
+
'''
|
|
79
|
+
if datum not in (None, self._datum):
|
|
80
|
+
self._datum = _xellipsoidal(datum=_ellipsoidal_datum(datum, name=name))
|
|
81
|
+
if name:
|
|
82
|
+
self.name = name
|
|
83
|
+
|
|
84
|
+
self.reset(lat0, lon0)
|
|
85
|
+
|
|
86
|
+
@Property
|
|
87
|
+
def datum(self):
|
|
88
|
+
'''Get the datum (L{Datum}).
|
|
89
|
+
'''
|
|
90
|
+
return self._datum
|
|
91
|
+
|
|
92
|
+
@datum.setter # PYCHOK setter!
|
|
93
|
+
def datum(self, datum):
|
|
94
|
+
'''Set the datum or ellipsoid (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}
|
|
95
|
+
or L{a_f2Tuple}) or C{None} for the default.
|
|
96
|
+
'''
|
|
97
|
+
d = CassiniSoldner._datum if datum is None else \
|
|
98
|
+
_xellipsoidal(datum=_ellipsoidal_datum(datum, name=self.name))
|
|
99
|
+
if self._datum != d:
|
|
100
|
+
self._datum = d
|
|
101
|
+
self.geodesic = None if self._geodesic is None else self.isExact
|
|
102
|
+
|
|
103
|
+
def _datumatch(self, latlon):
|
|
104
|
+
'''Check for matching datum ellipsoids.
|
|
105
|
+
|
|
106
|
+
@raise CSSError: Ellipsoid mismatch of B{C{latlon}} and this projection.
|
|
107
|
+
'''
|
|
108
|
+
d = _xattr(latlon, datum=None)
|
|
109
|
+
if d:
|
|
110
|
+
_xdatum(self.datum, d, Error=CSSError)
|
|
111
|
+
|
|
112
|
+
@Property_RO
|
|
113
|
+
def equatoradius(self):
|
|
114
|
+
'''Get the ellipsoid's equatorial radius, semi-axis (C{meter}).
|
|
115
|
+
'''
|
|
116
|
+
return self.geodesic.a
|
|
117
|
+
|
|
118
|
+
a = equatoradius
|
|
119
|
+
|
|
120
|
+
@Property_RO
|
|
121
|
+
def flattening(self):
|
|
122
|
+
'''Get the ellipsoid's flattening (C{scalar}).
|
|
123
|
+
'''
|
|
124
|
+
return self.geodesic.f
|
|
125
|
+
|
|
126
|
+
f = flattening
|
|
127
|
+
|
|
128
|
+
def forward(self, lat, lon, name=NN):
|
|
129
|
+
'''Convert an (ellipsoidal) geodetic location to Cassini-Soldner
|
|
130
|
+
easting and northing.
|
|
131
|
+
|
|
132
|
+
@arg lat: Latitude of the location (C{degrees90}).
|
|
133
|
+
@arg lon: Longitude of the location (C{degrees180}).
|
|
134
|
+
@kwarg name: Name inlieu of this projection's name (C{str}).
|
|
135
|
+
|
|
136
|
+
@return: An L{EasNor2Tuple}C{(easting, northing)}.
|
|
137
|
+
|
|
138
|
+
@see: Methods L{CassiniSoldner.forward4}, L{CassiniSoldner.reverse}
|
|
139
|
+
and L{CassiniSoldner.reverse4}.
|
|
140
|
+
|
|
141
|
+
@raise CSSError: Invalid B{C{lat}} or B{C{lon}}.
|
|
142
|
+
'''
|
|
143
|
+
t = self.forward6(lat, lon, name=name)
|
|
144
|
+
return EasNor2Tuple(t.easting, t.northing, name=t.name)
|
|
145
|
+
|
|
146
|
+
def forward4(self, lat, lon, name=NN):
|
|
147
|
+
'''Convert an (ellipsoidal) geodetic location to Cassini-Soldner
|
|
148
|
+
easting and northing.
|
|
149
|
+
|
|
150
|
+
@arg lat: Latitude of the location (C{degrees90}).
|
|
151
|
+
@arg lon: Longitude of the location (C{degrees180}).
|
|
152
|
+
@kwarg name: Name inlieu of this projection's name (C{str}).
|
|
153
|
+
|
|
154
|
+
@return: An L{EasNorAziRk4Tuple}C{(easting, northing,
|
|
155
|
+
azimuth, reciprocal)}.
|
|
156
|
+
|
|
157
|
+
@see: Method L{CassiniSoldner.forward}, L{CassiniSoldner.forward6},
|
|
158
|
+
L{CassiniSoldner.reverse} and L{CassiniSoldner.reverse4}.
|
|
159
|
+
|
|
160
|
+
@raise CSSError: Invalid B{C{lat}} or B{C{lon}}.
|
|
161
|
+
'''
|
|
162
|
+
t = self.forward6(lat, lon, name=name)
|
|
163
|
+
return EasNorAziRk4Tuple(t.easting, t.northing,
|
|
164
|
+
t.azimuth, t.reciprocal, name=t.name)
|
|
165
|
+
|
|
166
|
+
def forward6(self, lat, lon, name=NN):
|
|
167
|
+
'''Convert an (ellipsoidal) geodetic location to Cassini-Soldner
|
|
168
|
+
easting and northing.
|
|
169
|
+
|
|
170
|
+
@arg lat: Latitude of the location (C{degrees90}).
|
|
171
|
+
@arg lon: Longitude of the location (C{degrees180}).
|
|
172
|
+
@kwarg name: Name inlieu of this projection's name (C{str}).
|
|
173
|
+
|
|
174
|
+
@return: An L{EasNorAziRkEqu6Tuple}C{(easting, northing,
|
|
175
|
+
azimuth, reciprocal, equatorarc, equatorazimuth)}.
|
|
176
|
+
|
|
177
|
+
@see: Method L{CassiniSoldner.forward}, L{CassiniSoldner.forward4},
|
|
178
|
+
L{CassiniSoldner.reverse} and L{CassiniSoldner.reverse4}.
|
|
179
|
+
|
|
180
|
+
@raise CSSError: Invalid B{C{lat}} or B{C{lon}}.
|
|
181
|
+
'''
|
|
182
|
+
g = self.geodesic
|
|
183
|
+
|
|
184
|
+
lat = Lat_(lat, Error=CSSError)
|
|
185
|
+
d, _ = _diff182(self.lon0, Lon_(lon, Error=CSSError)) # _2sum
|
|
186
|
+
D = fabs(d)
|
|
187
|
+
|
|
188
|
+
r = g.Inverse(lat, -D, lat, D)
|
|
189
|
+
z1, a = r.azi1, (r.a12 * _0_5)
|
|
190
|
+
z2, e = r.azi2, (r.s12 * _0_5)
|
|
191
|
+
if e == 0: # PYCHOK no cover
|
|
192
|
+
z = _diff182(z1, z2)[0] * _0_5 # _2sum
|
|
193
|
+
c = _copysign(_90_0, 90 - D) # -90 if D > 90 else 90
|
|
194
|
+
z1, z2 = c - z, c + z
|
|
195
|
+
if _signBit(d):
|
|
196
|
+
a, e, z2 = neg(a), neg(e), z1
|
|
197
|
+
|
|
198
|
+
z = _norm180(z2) # azimuth of easting direction
|
|
199
|
+
p = g.Line(lat, d, z, g.DISTANCE | g.GEODESICSCALE | g.LINE_OFF)
|
|
200
|
+
# reciprocal of azimuthal northing scale
|
|
201
|
+
rk = p.ArcPosition(neg(a), g.GEODESICSCALE).M21
|
|
202
|
+
# rk = p._GenPosition(True, -a, g.DISTANCE)[7]
|
|
203
|
+
|
|
204
|
+
s, c = _sincos2d(p.azi0) # aka equatorazimuth
|
|
205
|
+
sb1 = _copysign(c, lat)
|
|
206
|
+
cb1 = _copysign(s, 90 - D) # -abs(s) if D > 90 else abs(s)
|
|
207
|
+
d = _atan2d(sb1 * self._cb0 - cb1 * self._sb0,
|
|
208
|
+
cb1 * self._cb0 + sb1 * self._sb0)
|
|
209
|
+
n = self._meridian.ArcPosition(d, g.DISTANCE).s12
|
|
210
|
+
# n = self._meridian._GenPosition(True, d, g.DISTANCE)[4]
|
|
211
|
+
return EasNorAziRkEqu6Tuple(e, n, z, rk, p.a1, p.azi0,
|
|
212
|
+
name=name or self.name)
|
|
213
|
+
|
|
214
|
+
@Property
|
|
215
|
+
def geodesic(self):
|
|
216
|
+
'''Get this projection's I{wrapped} U{geodesic.Geodesic
|
|
217
|
+
<https://GeographicLib.SourceForge.io/Python/doc/code.html>}, provided
|
|
218
|
+
I{Karney}'s U{geographiclib<https://PyPI.org/project/geographiclib>}
|
|
219
|
+
package is installed, otherwise an I{exact} L{GeodesicExact} instance.
|
|
220
|
+
'''
|
|
221
|
+
g = self._geodesic
|
|
222
|
+
if g is None:
|
|
223
|
+
E = self.datum.ellipsoid
|
|
224
|
+
try:
|
|
225
|
+
g = E.geodesicw
|
|
226
|
+
except ImportError:
|
|
227
|
+
g = E.geodesicx
|
|
228
|
+
self._geodesic = g
|
|
229
|
+
return g
|
|
230
|
+
|
|
231
|
+
@geodesic.setter # PYCHOK setter!
|
|
232
|
+
def geodesic(self, exact):
|
|
233
|
+
'''Set this projection's geodesic (C{bool}) to L{GeodesicExact}
|
|
234
|
+
or I{wrapped Karney}'s or C{None} for the default.
|
|
235
|
+
|
|
236
|
+
@raise ImportError: Package U{geographiclib<https://PyPI.org/
|
|
237
|
+
project/geographiclib>} not installed or
|
|
238
|
+
not found and C{B{exact}=False}.
|
|
239
|
+
'''
|
|
240
|
+
E = self.datum.ellipsoid
|
|
241
|
+
self._geodesic = None if exact is None else (
|
|
242
|
+
E.geodesicx if exact else E.geodesicw)
|
|
243
|
+
self.reset(*self.latlon0)
|
|
244
|
+
|
|
245
|
+
@Property_RO
|
|
246
|
+
def isExact(self):
|
|
247
|
+
'''Return C{True} if this projection's geodesic is L{GeodesicExact}.
|
|
248
|
+
'''
|
|
249
|
+
return isinstance(self.geodesic, _MODS.geodesicx.GeodesicExact)
|
|
250
|
+
|
|
251
|
+
@Property_RO
|
|
252
|
+
def lat0(self):
|
|
253
|
+
'''Get the center latitude (C{degrees90}).
|
|
254
|
+
'''
|
|
255
|
+
return self.latlon0.lat
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def latlon0(self):
|
|
259
|
+
'''Get the center lat- and longitude (L{LatLon2Tuple}C{(lat, lon)})
|
|
260
|
+
in (C{degrees90}, (C{degrees180}).
|
|
261
|
+
'''
|
|
262
|
+
return self._latlon0
|
|
263
|
+
|
|
264
|
+
@latlon0.setter # PYCHOK setter!
|
|
265
|
+
def latlon0(self, latlon0):
|
|
266
|
+
'''Set the center lat- and longitude (ellipsoidal C{LatLon},
|
|
267
|
+
L{LatLon2Tuple}, L{LatLon4Tuple} or a C{tuple} or C{list}
|
|
268
|
+
with the C{lat}- and C{lon}gitude in C{degrees}).
|
|
269
|
+
|
|
270
|
+
@raise CSSError: Invalid B{C{latlon0}} or ellipsoid mismatch
|
|
271
|
+
of B{C{latlon0}} and this projection.
|
|
272
|
+
'''
|
|
273
|
+
if islistuple(latlon0, 2):
|
|
274
|
+
lat0, lon0 = latlon0[:2]
|
|
275
|
+
else:
|
|
276
|
+
try:
|
|
277
|
+
lat0, lon0 = latlon0.lat, latlon0.lon
|
|
278
|
+
self._datumatch(latlon0)
|
|
279
|
+
except (AttributeError, TypeError, ValueError) as x:
|
|
280
|
+
raise CSSError(latlon0=latlon0, cause=x)
|
|
281
|
+
self.reset(lat0, lon0)
|
|
282
|
+
|
|
283
|
+
@Property_RO
|
|
284
|
+
def lon0(self):
|
|
285
|
+
'''Get the center longitude (C{degrees180}).
|
|
286
|
+
'''
|
|
287
|
+
return self.latlon0.lon
|
|
288
|
+
|
|
289
|
+
@deprecated_Property_RO
|
|
290
|
+
def majoradius(self): # PYCHOK no cover
|
|
291
|
+
'''DEPRECATED, use property C{equatoradius}.'''
|
|
292
|
+
return self.equatoradius
|
|
293
|
+
|
|
294
|
+
def reset(self, lat0, lon0):
|
|
295
|
+
'''Set or reset the center point of this Cassini-Soldner projection.
|
|
296
|
+
|
|
297
|
+
@arg lat0: Center point latitude (C{degrees90}).
|
|
298
|
+
@arg lon0: Center point longitude (C{degrees180}).
|
|
299
|
+
|
|
300
|
+
@raise CSSError: Invalid B{C{lat0}} or B{C{lon0}}.
|
|
301
|
+
'''
|
|
302
|
+
_update_all(self)
|
|
303
|
+
|
|
304
|
+
g = self.geodesic
|
|
305
|
+
self._meridian = m = g.Line(Lat_(lat0=lat0, Error=CSSError),
|
|
306
|
+
Lon_(lon0=lon0, Error=CSSError), _0_0,
|
|
307
|
+
g.STANDARD | g.DISTANCE_IN | g.LINE_OFF)
|
|
308
|
+
self._latlon0 = LatLon2Tuple(m.lat1, m.lon1)
|
|
309
|
+
s, c = _sincos2d(m.lat1) # == self.lat0 == self.LatitudeOrigin()
|
|
310
|
+
self._sb0, self._cb0 = _norm2(s * g.f1, c)
|
|
311
|
+
|
|
312
|
+
def reverse(self, easting, northing, name=NN, LatLon=None, **LatLon_kwds):
|
|
313
|
+
'''Convert a Cassini-Soldner location to (ellipsoidal) geodetic
|
|
314
|
+
lat- and longitude.
|
|
315
|
+
|
|
316
|
+
@arg easting: Easting of the location (C{meter}).
|
|
317
|
+
@arg northing: Northing of the location (C{meter}).
|
|
318
|
+
@kwarg name: Name inlieu of this projection's name (C{str}).
|
|
319
|
+
@kwarg LatLon: Optional, ellipsoidal class to return the
|
|
320
|
+
geodetic location as (C{LatLon}) or C{None}.
|
|
321
|
+
@kwarg LatLon_kwds: Optional (C{LatLon}) keyword arguments,
|
|
322
|
+
ignored if C{B{LatLon} is None}.
|
|
323
|
+
|
|
324
|
+
@return: Geodetic location B{C{LatLon}} or if B{C{LatLon}}
|
|
325
|
+
is C{None}, a L{LatLon2Tuple}C{(lat, lon)}.
|
|
326
|
+
|
|
327
|
+
@raise CSSError: Ellipsoidal mismatch of B{C{LatLon}} and this projection.
|
|
328
|
+
|
|
329
|
+
@raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_kwds}}.
|
|
330
|
+
|
|
331
|
+
@see: Method L{CassiniSoldner.reverse4}, L{CassiniSoldner.forward}.
|
|
332
|
+
L{CassiniSoldner.forward4} and L{CassiniSoldner.forward6}.
|
|
333
|
+
'''
|
|
334
|
+
r = self.reverse4(easting, northing, name=name)
|
|
335
|
+
if LatLon is None:
|
|
336
|
+
r = LatLon2Tuple(r.lat, r.lon, name=r.name) # PYCHOK expected
|
|
337
|
+
else:
|
|
338
|
+
_xsubclassof(_LLEB, LatLon=LatLon)
|
|
339
|
+
kwds = _xkwds(LatLon_kwds, datum=self.datum, name=r.name)
|
|
340
|
+
r = LatLon(r.lat, r.lon, **kwds) # PYCHOK expected
|
|
341
|
+
self._datumatch(r)
|
|
342
|
+
return r
|
|
343
|
+
|
|
344
|
+
def reverse4(self, easting, northing, name=NN):
|
|
345
|
+
'''Convert a Cassini-Soldner location to (ellipsoidal) geodetic
|
|
346
|
+
lat- and longitude.
|
|
347
|
+
|
|
348
|
+
@arg easting: Easting of the location (C{meter}).
|
|
349
|
+
@arg northing: Northing of the location (C{meter}).
|
|
350
|
+
@kwarg name: Name inlieu of this projection's name (C{str}).
|
|
351
|
+
|
|
352
|
+
@return: A L{LatLonAziRk4Tuple}C{(lat, lon, azimuth, reciprocal)}.
|
|
353
|
+
|
|
354
|
+
@see: Method L{CassiniSoldner.reverse}, L{CassiniSoldner.forward}
|
|
355
|
+
and L{CassiniSoldner.forward4}.
|
|
356
|
+
'''
|
|
357
|
+
g = self.geodesic
|
|
358
|
+
n = self._meridian.Position(northing)
|
|
359
|
+
r = g.Direct(n.lat2, n.lon2, n.azi2 + _90_0, easting, outmask=g.STANDARD | g.GEODESICSCALE)
|
|
360
|
+
z = _umod_360(r.azi2) # -180 <= r.azi2 < 180 ... 0 <= z < 360
|
|
361
|
+
# include z azimuth of easting direction and rk reciprocal
|
|
362
|
+
# of azimuthal northing scale (see C++ member Direct() 5/6
|
|
363
|
+
# <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Geodesic.html>)
|
|
364
|
+
return LatLonAziRk4Tuple(r.lat2, r.lon2, z, r.M12, name=name or self.name)
|
|
365
|
+
|
|
366
|
+
toLatLon = reverse # XXX not reverse4
|
|
367
|
+
|
|
368
|
+
def toRepr(self, prec=6, **unused): # PYCHOK expected
|
|
369
|
+
'''Return a string representation of this projection.
|
|
370
|
+
|
|
371
|
+
@kwarg prec: Number of (decimal) digits, unstripped (C{int}).
|
|
372
|
+
|
|
373
|
+
@return: This projection as C{"<classname>(lat0, lon0, ...)"}
|
|
374
|
+
(C{str}).
|
|
375
|
+
'''
|
|
376
|
+
return _fstrLL0(self, prec, True)
|
|
377
|
+
|
|
378
|
+
def toStr(self, prec=6, sep=_SPACE_, **unused): # PYCHOK expected
|
|
379
|
+
'''Return a string representation of this projection.
|
|
380
|
+
|
|
381
|
+
@kwarg prec: Number of (decimal) digits, unstripped (C{int}).
|
|
382
|
+
@kwarg sep: Separator to join (C{str}).
|
|
383
|
+
|
|
384
|
+
@return: This projection as C{"lat0 lon0"} (C{str}).
|
|
385
|
+
'''
|
|
386
|
+
t = _fstrLL0(self, prec, False)
|
|
387
|
+
return t if sep is None else sep.join(t)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class Css(_NamedBase):
|
|
391
|
+
'''Cassini-Soldner East-/Northing location.
|
|
392
|
+
'''
|
|
393
|
+
_CS0 = None # default projection (L{CassiniSoldner})
|
|
394
|
+
_cs0 = None # projection (L{CassiniSoldner})
|
|
395
|
+
_easting = _0_0 # easting (C{float})
|
|
396
|
+
_height = 0 # height (C{meter})
|
|
397
|
+
_northing = _0_0 # northing (C{float})
|
|
398
|
+
|
|
399
|
+
def __init__(self, e, n, h=0, cs0=None, name=NN):
|
|
400
|
+
'''New L{Css} Cassini-Soldner position.
|
|
401
|
+
|
|
402
|
+
@arg e: Easting (C{meter}).
|
|
403
|
+
@arg n: Northing (C{meter}).
|
|
404
|
+
@kwarg h: Optional height (C{meter}).
|
|
405
|
+
@kwarg cs0: Optional, the Cassini-Soldner projection
|
|
406
|
+
(L{CassiniSoldner}).
|
|
407
|
+
@kwarg name: Optional name (C{str}).
|
|
408
|
+
|
|
409
|
+
@return: The Cassini-Soldner location (L{Css}).
|
|
410
|
+
|
|
411
|
+
@raise CSSError: If B{C{e}} or B{C{n}} is invalid.
|
|
412
|
+
|
|
413
|
+
@raise TypeError: If B{C{cs0}} is not L{CassiniSoldner}.
|
|
414
|
+
|
|
415
|
+
@raise ValueError: Invalid B{C{h}}.
|
|
416
|
+
'''
|
|
417
|
+
self._cs0 = _CS0(cs0)
|
|
418
|
+
self._easting = Easting(e, Error=CSSError)
|
|
419
|
+
self._northing = Northing(n, Error=CSSError)
|
|
420
|
+
if h:
|
|
421
|
+
self._height = Height(h=h)
|
|
422
|
+
if name:
|
|
423
|
+
self.name = name
|
|
424
|
+
|
|
425
|
+
@Property_RO
|
|
426
|
+
def azi(self):
|
|
427
|
+
'''Get the azimuth of easting direction (C{degrees}).
|
|
428
|
+
'''
|
|
429
|
+
return self.reverse4.azimuth
|
|
430
|
+
|
|
431
|
+
azimuth = azi
|
|
432
|
+
|
|
433
|
+
@Property
|
|
434
|
+
def cs0(self):
|
|
435
|
+
'''Get the projection (L{CassiniSoldner}).
|
|
436
|
+
'''
|
|
437
|
+
return self._cs0 or Css._CS0
|
|
438
|
+
|
|
439
|
+
@cs0.setter # PYCHOK setter!
|
|
440
|
+
def cs0(self, cs0):
|
|
441
|
+
'''Set the I{Cassini-Soldner} projection (L{CassiniSoldner}).
|
|
442
|
+
|
|
443
|
+
@raise TypeError: Invalid B{C{cs0}}.
|
|
444
|
+
'''
|
|
445
|
+
cs0 = _CS0(cs0)
|
|
446
|
+
if cs0 != self._cs0:
|
|
447
|
+
_update_all(self)
|
|
448
|
+
self._cs0 = cs0
|
|
449
|
+
|
|
450
|
+
# def dup(self, name=NN, **e_n_h_cs0): # PYCHOK signature
|
|
451
|
+
# '''Duplicate this position with some attributes modified.
|
|
452
|
+
#
|
|
453
|
+
# @kwarg e_n_h_cs0: Use keyword argument C{B{e}=...}, C{B{n}=...},
|
|
454
|
+
# C{B{h}=...} and/or C{B{cs0}=...} to override
|
|
455
|
+
# the current C{easting}, C{northing} C{height}
|
|
456
|
+
# or C{cs0} projectio, respectively.
|
|
457
|
+
# '''
|
|
458
|
+
# def _args_kwds(e=None, n=None, **kwds):
|
|
459
|
+
# return (e, n), kwds
|
|
460
|
+
#
|
|
461
|
+
# kwds = _xkwds(e_n_h_cs0, e=self.easting, n=self.northing,
|
|
462
|
+
# h=self.height, cs0=self.cs0,
|
|
463
|
+
# name=name or self.name)
|
|
464
|
+
# args, kwds = _args_kwds(**kwds)
|
|
465
|
+
# return self.__class__(*args, **kwds) # .classof
|
|
466
|
+
|
|
467
|
+
@Property_RO
|
|
468
|
+
def easting(self):
|
|
469
|
+
'''Get the easting (C{meter}).
|
|
470
|
+
'''
|
|
471
|
+
return self._easting
|
|
472
|
+
|
|
473
|
+
@Property_RO
|
|
474
|
+
def height(self):
|
|
475
|
+
'''Get the height (C{meter}).
|
|
476
|
+
'''
|
|
477
|
+
return self._height
|
|
478
|
+
|
|
479
|
+
@Property_RO
|
|
480
|
+
def latlon(self):
|
|
481
|
+
'''Get the lat- and longitude (L{LatLon2Tuple}).
|
|
482
|
+
'''
|
|
483
|
+
r = self.reverse4
|
|
484
|
+
return LatLon2Tuple(r.lat, r.lon, name=self.name)
|
|
485
|
+
|
|
486
|
+
@Property_RO
|
|
487
|
+
def northing(self):
|
|
488
|
+
'''Get the northing (C{meter}).
|
|
489
|
+
'''
|
|
490
|
+
return self._northing
|
|
491
|
+
|
|
492
|
+
@Property_RO
|
|
493
|
+
def reverse4(self):
|
|
494
|
+
'''Get the lat, lon, azimuth and reciprocal (L{LatLonAziRk4Tuple}).
|
|
495
|
+
'''
|
|
496
|
+
return self.cs0.reverse4(self.easting, self.northing, name=self.name)
|
|
497
|
+
|
|
498
|
+
@Property_RO
|
|
499
|
+
def rk(self):
|
|
500
|
+
'''Get the reciprocal of azimuthal northing scale (C{scalar}).
|
|
501
|
+
'''
|
|
502
|
+
return self.reverse4.reciprocal
|
|
503
|
+
|
|
504
|
+
reciprocal = rk
|
|
505
|
+
|
|
506
|
+
def toLatLon(self, LatLon=None, height=None, **LatLon_kwds):
|
|
507
|
+
'''Convert this L{Css} to an (ellipsoidal) geodetic point.
|
|
508
|
+
|
|
509
|
+
@kwarg LatLon: Optional, ellipsoidal class to return the
|
|
510
|
+
geodetic point (C{LatLon}) or C{None}.
|
|
511
|
+
@kwarg height: Optional height for the point, overriding the
|
|
512
|
+
default height (C{meter}).
|
|
513
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
|
|
514
|
+
arguments, ignored if C{B{LatLon} is None}.
|
|
515
|
+
|
|
516
|
+
@return: The geodetic point (B{C{LatLon}}) or if B{C{LatLon}}
|
|
517
|
+
is C{None}, a L{LatLon4Tuple}C{(lat, lon, height,
|
|
518
|
+
datum)}.
|
|
519
|
+
|
|
520
|
+
@raise TypeError: If B{C{LatLon}} or B{C{datum}} is not
|
|
521
|
+
ellipsoidal or invalid B{C{height}} or
|
|
522
|
+
B{C{LatLon_kwds}}.
|
|
523
|
+
'''
|
|
524
|
+
if LatLon:
|
|
525
|
+
_xsubclassof(_LLEB, LatLon=LatLon)
|
|
526
|
+
|
|
527
|
+
lat, lon = self.latlon
|
|
528
|
+
h = _heigHt(self, height)
|
|
529
|
+
return _LL4Tuple(lat, lon, h, self.cs0.datum, LatLon, LatLon_kwds,
|
|
530
|
+
inst=self, name=self.name)
|
|
531
|
+
|
|
532
|
+
def toRepr(self, prec=6, fmt=Fmt.SQUARE, sep=_COMMASPACE_, m=_m_, C=False): # PYCHOK expected
|
|
533
|
+
'''Return a string representation of this L{Css} position.
|
|
534
|
+
|
|
535
|
+
@kwarg prec: Number of (decimal) digits, unstripped (C{int}).
|
|
536
|
+
@kwarg fmt: Enclosing backets format (C{str}).
|
|
537
|
+
@kwarg sep: Optional separator between name:values (C{str}).
|
|
538
|
+
@kwarg m: Optional unit of the height, default meter (C{str}).
|
|
539
|
+
@kwarg C: Optionally, include name of projection (C{bool}).
|
|
540
|
+
|
|
541
|
+
@return: This position as C{"[E:meter, N:meter, H:m, name:'',
|
|
542
|
+
C:Conic.Datum]"} (C{str}).
|
|
543
|
+
'''
|
|
544
|
+
t, T = _fstrENH2(self, prec, m)
|
|
545
|
+
if self.name:
|
|
546
|
+
t += repr(self.name),
|
|
547
|
+
T += _name_,
|
|
548
|
+
if C:
|
|
549
|
+
t += self.cs0.toRepr(prec=prec),
|
|
550
|
+
T += _C_,
|
|
551
|
+
return _xzipairs(T, t, sep=sep, fmt=fmt)
|
|
552
|
+
|
|
553
|
+
def toStr(self, prec=6, sep=_SPACE_, m=_m_): # PYCHOK expected
|
|
554
|
+
'''Return a string representation of this L{Css} position.
|
|
555
|
+
|
|
556
|
+
@kwarg prec: Number of (decimal) digits, unstripped (C{int}).
|
|
557
|
+
@kwarg sep: Optional separator to join (C{str}) or C{None}
|
|
558
|
+
to return an unjoined C{tuple} of C{str}s.
|
|
559
|
+
@kwarg m: Height units, default C{meter} (C{str}).
|
|
560
|
+
|
|
561
|
+
@return: This position as C{"easting nothing"} C{str} in
|
|
562
|
+
C{meter} plus C{" height"} and C{'m'} if height
|
|
563
|
+
is non-zero (C{str}).
|
|
564
|
+
'''
|
|
565
|
+
t, _ = _fstrENH2(self, prec, m)
|
|
566
|
+
return t if sep is None else sep.join(t)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
class EasNorAziRk4Tuple(_NamedTuple):
|
|
570
|
+
'''4-Tuple C{(easting, northing, azimuth, reciprocal)} for the
|
|
571
|
+
Cassini-Soldner location with C{easting} and C{northing} in
|
|
572
|
+
C{meters} and the C{azimuth} of easting direction and
|
|
573
|
+
C{reciprocal} of azimuthal northing scale, both in C{degrees}.
|
|
574
|
+
'''
|
|
575
|
+
_Names_ = (_easting_, _northing_, _azimuth_, _reciprocal_)
|
|
576
|
+
_Units_ = ( Easting, Northing, Bearing, Scalar)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
class EasNorAziRkEqu6Tuple(_NamedTuple):
|
|
580
|
+
'''6-Tuple C{(easting, northing, azimuth, reciprocal, equatorarc,
|
|
581
|
+
equatorazimuth)} for the Cassini-Soldner location with
|
|
582
|
+
C{easting} and C{northing} in C{meters} and the C{azimuth} of
|
|
583
|
+
easting direction, C{reciprocal} of azimuthal northing scale,
|
|
584
|
+
C{equatorarc} and C{equatorazimuth}, all in C{degrees}.
|
|
585
|
+
'''
|
|
586
|
+
_Names_ = EasNorAziRk4Tuple._Names_ + ('equatorarc', 'equatorazimuth')
|
|
587
|
+
_Units_ = EasNorAziRk4Tuple._Units_ + ( Degrees, Bearing)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
class LatLonAziRk4Tuple(_NamedTuple):
|
|
591
|
+
'''4-Tuple C{(lat, lon, azimuth, reciprocal)}, all in C{degrees}
|
|
592
|
+
where C{azimuth} is the azimuth of easting direction and
|
|
593
|
+
C{reciprocal} the reciprocal of azimuthal northing scale.
|
|
594
|
+
'''
|
|
595
|
+
_Names_ = (_lat_, _lon_, _azimuth_, _reciprocal_)
|
|
596
|
+
_Units_ = ( Lat_, Lon_, Bearing, Scalar)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def toCss(latlon, cs0=None, height=None, Css=Css, name=NN):
|
|
600
|
+
'''Convert an (ellipsoidal) geodetic point to a Cassini-Soldner
|
|
601
|
+
location.
|
|
602
|
+
|
|
603
|
+
@arg latlon: Ellipsoidal point (C{LatLon} or L{LatLon4Tuple}).
|
|
604
|
+
@kwarg cs0: Optional, the Cassini-Soldner projection to use
|
|
605
|
+
(L{CassiniSoldner}).
|
|
606
|
+
@kwarg height: Optional height for the point, overriding the
|
|
607
|
+
default height (C{meter}).
|
|
608
|
+
@kwarg Css: Optional class to return the location (L{Css}) or C{None}.
|
|
609
|
+
@kwarg name: Optional B{C{Css}} name (C{str}).
|
|
610
|
+
|
|
611
|
+
@return: The Cassini-Soldner location (B{C{Css}}) or an
|
|
612
|
+
L{EasNor3Tuple}C{(easting, northing, height)}
|
|
613
|
+
if B{C{Css}} is C{None}.
|
|
614
|
+
|
|
615
|
+
@raise CSSError: Ellipsoidal mismatch of B{C{latlon}} and B{C{cs0}}.
|
|
616
|
+
|
|
617
|
+
@raise ImportError: Package U{geographiclib<https://PyPI.org/
|
|
618
|
+
project/geographiclib>} not installed or
|
|
619
|
+
not found.
|
|
620
|
+
|
|
621
|
+
@raise TypeError: If B{C{latlon}} is not ellipsoidal.
|
|
622
|
+
'''
|
|
623
|
+
_xinstanceof(_LLEB, LatLon4Tuple, latlon=latlon)
|
|
624
|
+
|
|
625
|
+
cs = _CS0(cs0)
|
|
626
|
+
cs._datumatch(latlon)
|
|
627
|
+
|
|
628
|
+
c = cs.forward4(latlon.lat, latlon.lon)
|
|
629
|
+
h = _heigHt(latlon, height)
|
|
630
|
+
n = name or nameof(latlon)
|
|
631
|
+
|
|
632
|
+
if Css is None:
|
|
633
|
+
r = EasNor3Tuple(c.easting, c.northing, h, name=n)
|
|
634
|
+
else:
|
|
635
|
+
r = Css(c.easting, c.northing, h=h, cs0=cs, name=n)
|
|
636
|
+
r._latlon = LatLon2Tuple(latlon.lat, latlon.lon, name=n)
|
|
637
|
+
r._azi, r._rk = c.azimuth, c.reciprocal
|
|
638
|
+
return r
|
|
639
|
+
|
|
640
|
+
# **) MIT License
|
|
641
|
+
#
|
|
642
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
643
|
+
#
|
|
644
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
645
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
646
|
+
# to deal in the Software without restriction, including without limitation
|
|
647
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
648
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
649
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
650
|
+
#
|
|
651
|
+
# The above copyright notice and this permission notice shall be included
|
|
652
|
+
# in all copies or substantial portions of the Software.
|
|
653
|
+
#
|
|
654
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
655
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
656
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
657
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
658
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
659
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
660
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|