Coverage for pygeodesy / lcc.py: 96%
288 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-07-21 14:54 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-07-21 14:54 -0400
2# -*- coding: utf-8 -*-
4u'''Lambert Conformal Conic (LCC) projection.
6Lambert conformal conic projection for 1- or 2-Standard Parallels classes L{Conic}, L{Conics} registry, L{LCCError}
7and position class L{Lcc}.
9See U{LCC<https://WikiPedia.org/wiki/Lambert_conformal_conic_projection>}, U{Lambert
10Conformal Conic to Geographic Transformation Formulae
11<https://www.Linz.govt.NZ/data/geodetic-system/coordinate-conversion/projection-conversions/lambert-conformal-conic-geographic>},
12U{Lambert Conformal Conic Projection<https://MathWorld.Wolfram.com/LambertConformalConicProjection.html>}
13and John P. Snyder U{'Map Projections - A Working Manual'<https://Pubs.USGS.gov/pp/1395/report.pdf>}, 1987, pp 107-109.
15@var Conics.Be08Lb: Conic(name='Be08Lb', lat0=50.797815, lon0=4.35921583, par1=49.8333339, par2=51.1666672, E0=649328, N0=665262, k0=1, SP=2, datum=Datum(name='GRS80', ellipsoid=Ellipsoids.GRS80, transform=Transforms.WGS84),
16@var Conics.Be72Lb: Conic(name='Be72Lb', lat0=90, lon0=4.3674867, par1=49.8333339, par2=51.1666672, E0=150000.013, N0=5400088.438, k0=1, SP=2, datum=Datum(name='NAD83', ellipsoid=Ellipsoids.GRS80, transform=Transforms.NAD83),
17@var Conics.Fr93Lb: Conic(name='Fr93Lb', lat0=46.5, lon0=3, par1=49, par2=44, E0=700000, N0=6600000, k0=1, SP=2, datum=Datum(name='WGS84', ellipsoid=Ellipsoids.WGS84, transform=Transforms.WGS84),
18@var Conics.MaNLb: Conic(name='MaNLb', lat0=33.3, lon0=-5.4, par1=31.73, par2=34.87, E0=500000, N0=300000, k0=1, SP=2, datum=Datum(name='NTF', ellipsoid=Ellipsoids.Clarke1880IGN, transform=Transforms.NTF),
19@var Conics.MxLb: Conic(name='MxLb', lat0=12, lon0=-102, par1=17.5, par2=29.5, E0=2500000, N0=0, k0=1, SP=2, datum=Datum(name='WGS84', ellipsoid=Ellipsoids.WGS84, transform=Transforms.WGS84),
20@var Conics.PyT_Lb: Conic(name='PyT_Lb', lat0=46.8, lon0=2.33722917, par1=45.8989389, par2=47.6960144, E0=600000, N0=200000, k0=1, SP=2, datum=Datum(name='NTF', ellipsoid=Ellipsoids.Clarke1880IGN, transform=Transforms.NTF),
21@var Conics.USA_Lb: Conic(name='USA_Lb', lat0=23, lon0=-96, par1=33, par2=45, E0=0, N0=0, k0=1, SP=2, datum=Datum(name='WGS84', ellipsoid=Ellipsoids.WGS84, transform=Transforms.WGS84),
22@var Conics.WRF_Lb: Conic(name='WRF_Lb', lat0=40, lon0=-97, par1=33, par2=45, E0=0, N0=0, k0=1, SP=2, datum=Datum(name='WGS84', ellipsoid=Ellipsoids.WGS84, transform=Transforms.WGS84)
23'''
24# make sure int/int division yields float quotient, see .basics
25from __future__ import division as _; del _ # noqa: E702 ;
27from pygeodesy.basics import copysign0, _isin, typename, \
28 _xinstanceof, _xsubclassof
29from pygeodesy.constants import EPS, EPS02, PI_2, _float as _F, \
30 _0_0, _0_5, _1_0, _2_0, _90_0
31from pygeodesy.ellipsoidalBase import LatLonEllipsoidalBase as _LLEB
32from pygeodesy.datums import Datums, _ellipsoidal_datum
33from pygeodesy.errors import _IsnotError, _ValueError
34from pygeodesy.fmath import hypot, _ALL_LAZY
35# from pygeodesy.internals import typename # from .basics
36from pygeodesy.interns import NN, _COMMASPACE_, _DMAIN_, _ED50_, _ellipsoidal_, \
37 _GRS80_, _k0_, _lat0_, _lon0_, _m_, _NTF_, _SPACE_, \
38 _WGS84_, _C_ # PYCHOK used!
39# from pygeodesy.lazily import _ALL_LAZY # from .fmath
40from pygeodesy.named import _lazyNamedEnumItem as _lazy, _name2__, _NamedBase, \
41 _NamedEnum, _NamedEnumItem, _xnamed
42from pygeodesy.namedTuples import EasNor3Tuple, LatLonDatum3Tuple, \
43 LatLon2Tuple, _LL4Tuple, PhiLam2Tuple
44from pygeodesy.props import deprecated_method, Property, Property_RO, _update_all
45from pygeodesy.streprs import Fmt, _fstrENH2, _xzipairs
46from pygeodesy.units import Easting, Height, _heigHt, Lamd, Northing, Phid, \
47 Scalar_
48from pygeodesy.utily import atan1, degrees90, degrees180, sincos2, tanPI_2_2
50from math import atan, fabs, log, radians, sin, sqrt
52__all__ = _ALL_LAZY.lcc
53__version__ = '26.07.21'
55_E0_ = 'E0'
56_N0_ = 'N0'
57_par1_ = 'par1'
58_par2_ = 'par2'
59_SP_ = 'SP'
62class Conic(_NamedEnumItem):
63 '''Lambert conformal conic projection (1- or 2-SP).
64 '''
65 _auth = NN # authorization (C{str})
66 _datum = None # datum (L{Datum})
67 _name = NN # Conic.__name__, set below
69 _e = _0_0 # ellipsoid eccentricity (C{float})
70 _E0 = _0_0 # false easting (C{float})
71 _k0 = _1_0 # scale factor (C{float})
72 _N0 = _0_0 # false northing (C{float})
73 _SP = 0 # 1- or 2-SP (C{int})
75 _opt3 = _0_0 # optional, longitude (C{radians})
76 _par1 = _0_0 # 1st std parallel (C{radians})
77 _par2 = _0_0 # 2nd std parallel (C{radians})
78 _phi0 = _0_0 # origin lat (C{radians})
79 _lam0 = _0_0 # origin lon (C{radians})
81 _aF = _0_0 # precomputed F (C{float})
82 _n = _0_0 # precomputed n (C{float})
83 _1_n = _0_0 # precomputed 1 / n (C{float})
84 _r0 = _0_0 # precomputed rho0 (C{float})
86 def __init__(self, latlon0, par1, par2=None, E0=0, N0=0,
87 k0=1, opt3=0, auth=NN, **name):
88 '''New Lambert conformal conic projection.
90 @arg latlon0: Origin with (ellipsoidal) datum (C{LatLon}).
91 @arg par1: First standard parallel (C{degrees90}).
92 @kwarg par2: Optional, second standard parallel (C{degrees90}).
93 @kwarg E0: Optional, false easting (C{meter}).
94 @kwarg N0: Optional, false northing (C{meter}).
95 @kwarg k0: Optional scale factor (C{scalar}).
96 @kwarg opt3: Optional meridian (C{degrees180}).
97 @kwarg auth: Optional authentication authority (C{str}).
98 @kwarg name: Optional C{B{name}=NN} for the conic (C{str}).
100 @return: A Lambert projection (L{Conic}).
102 @raise TypeError: Non-ellipsoidal B{C{latlon0}}.
104 @raise ValueError: Invalid B{C{par1}}, B{C{par2}},
105 B{C{E0}}, B{C{N0}}, B{C{k0}}
106 or B{C{opt3}}.
107 '''
108 if latlon0 is not None:
109 _xinstanceof(_LLEB, latlon0=latlon0)
110 self._phi0, self._lam0 = latlon0.philam
112 self._par1 = Phid(par1=par1)
113 self._par2 = self._par1 if par2 is None else Phid(par2=par2)
115 if k0 != 1:
116 self._k0 = Scalar_(k0=k0)
117 if E0:
118 self._E0 = Northing(E0=E0, falsed=True)
119 if N0:
120 self._N0 = Easting(N0=N0, falsed=True)
121 if opt3:
122 self._opt3 = Lamd(opt3=opt3)
124 self.toDatum(latlon0.datum)._dup2(self)
125 self._register(Conics, name)
126 elif name:
127 self.name = name
128 if auth:
129 self._auth = str(auth)
131 @Property_RO
132 def auth(self):
133 '''Get the authentication authority (C{str}).
134 '''
135 return self._auth
137 @deprecated_method
138 def convertDatum(self, datum):
139 '''DEPRECATED, use method L{Conic.toDatum}.'''
140 return self.toDatum(datum)
142 @Property_RO
143 def datum(self):
144 '''Get the datum (L{Datum}).
145 '''
146 return self._datum
148 @Property_RO
149 def E0(self):
150 '''Get the false easting (C{meter}).
151 '''
152 return self._E0
154 def forward(self, latlon, lon=None, **height_name):
155 '''Convert I{geodetic} C{(lat, lon, height)} to (conformal) conic
156 C{easting} and C{northing)}.
158 @arg latlonh: Either a C{LatLon}, L{Lcc} or C{scalar} (geodetic)
159 latitude (C{degrees}).
160 @kwarg lon: The C{scalar} (geodetic) longitude (C{degrees}), required
161 if B{C{latlonh}} is C{scalar}, ignored otherwise.
162 @kwarg height_name: Optionally, a C{B{height}=None} overriding the
163 default height (C{meter}) and C{B{name}=NN} (C{str}).
165 @return: An L{Lcc}C{(easting, northing, ...)} with this C{conic},.
167 @raise LCCError: If B{C{latlonh}} not C{LatLon} L{Lcc}, C{scalar}
168 or invalid or if B{C{lon}} not C{scalar} for
169 C{scalar} B{C{latlonh}} or invalid or if B{C{height}}
170 invalid.
171 '''
172 try:
173 ll = _LLEB(latlon.lat, latlon.lon, datum=latlon.datum)
174 except AttributeError:
175 ll = _LLEB(latlon, lon, datum=self.datum)
176 return toLcc(ll, conic=self, **height_name)
178 @Property_RO
179 def k0(self):
180 '''Get scale factor (C{float}).
181 '''
182 return self._k0
184 @Property_RO
185 def lat0(self):
186 '''Get the origin latitude (C{degrees90}).
187 '''
188 return degrees90(self._phi0)
190 @Property_RO
191 def latlon0(self):
192 '''Get the central origin (L{LatLon2Tuple}C{(lat, lon)}).
193 '''
194 return LatLon2Tuple(self.lat0, self.lon0, name=self.name)
196 @Property_RO
197 def lam0(self):
198 '''Get the central meridian (C{radians}).
199 '''
200 return self._lam0
202 @Property_RO
203 def lon0(self):
204 '''Get the central meridian (C{degrees180}).
205 '''
206 return degrees180(self._lam0)
208 @Property_RO
209 def N0(self):
210 '''Get the false northing (C{meter}).
211 '''
212 return self._N0
214 @Property_RO
215 def name2(self):
216 '''Get the conic and datum names as "conic.datum" (C{str}).
217 '''
218 return self._DOT_(self.datum.name)
220 @Property_RO
221 def opt3(self):
222 '''Get the optional meridian (C{degrees180}).
223 '''
224 return degrees180(self._opt3)
226 @Property_RO
227 def par1(self):
228 '''Get the 1st standard parallel (C{degrees90}).
229 '''
230 return degrees90(self._par1)
232 @Property_RO
233 def par2(self):
234 '''Get the 2nd standard parallel (C{degrees90}).
235 '''
236 return degrees90(self._par2)
238 @Property_RO
239 def phi0(self):
240 '''Get the origin latitude (C{radians}).
241 '''
242 return self._phi0
244 @Property_RO
245 def philam0(self):
246 '''Get the central origin (L{PhiLam2Tuple}C{(phi, lam)}).
247 '''
248 return PhiLam2Tuple(self.phi0, self.lam0, name=self.name)
250 def reverse(self, enh, n=None, **height_name):
251 '''Convert I{conic} C{(easting, northing, height)} to geodetic C{lat-}
252 and C{longitude}.
254 @arg enh: Either an L{Lcc}, L{EasNor3Tuple} or C{scalar} easting
255 (C{meter}).
256 @kwarg n: The C{scalar} northing (C{meter}), required if B{C{enh}}
257 is C{scalar}, ignored otherwise.
258 @kwarg height_name: Optionally, a C{B{height}=None} overriding the
259 default height (C{meter}) and C{B{name}=NN} (C{str}).
261 @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)} with this
262 conic's C{datum}.
264 @raise LCCError: If B{C{enh}} not L{Lcc}, L{EasNor3Tuple}, C{scalar}
265 or invalid or if B{C{n}} not C{scalar} for C{scalar}
266 B{C{n}} or invalid or if B{C{height}} invalid.
267 '''
268 lcc = enh if isinstance(enh, Lcc) else (
269 Lcc(enh.easting, enh.northing, conic=self) if n is None else
270 Lcc(enh, n, conic=self))
271 return lcc.toLatLon(**height_name)
273 @Property_RO
274 def SP(self):
275 '''Get the number of standard parallels (C{int}).
276 '''
277 return self._SP
279 def toDatum(self, datum):
280 '''Convert this conic to the given datum.
282 @arg datum: Ellipsoidal datum to use (L{Datum}, L{Ellipsoid},
283 L{Ellipsoid2} or L{a_f2Tuple}).
285 @return: Converted conic, unregistered (L{Conic}).
287 @raise TypeError: Non-ellipsoidal B{C{datum}}.
288 '''
289 d = _ellipsoidal_datum(datum, name=self.name)
290 E = d.ellipsoid
291 if not E.isEllipsoidal:
292 raise _IsnotError(_ellipsoidal_, datum=datum)
294 c = self
295 if c._e != E.e or c._datum != d:
297 c = Conic(None, 0, name=self._name)
298 self._dup2(c)
299 c._datum = d
300 c._e = E.e
302 if fabs(c._par1 - c._par2) < EPS:
303 m1 = c._mdef(c._phi0)
304 t1 = c._tdef(c._phi0)
305 t0 = t1
306 k = 1 # _1_0
307 n = sin(c._phi0)
308 sp = 1
309 else:
310 m1 = c._mdef(c._par1)
311 m2 = c._mdef(c._par2)
312 t1 = c._tdef(c._par1)
313 t2 = c._tdef(c._par2)
314 t0 = c._tdef(c._phi0)
315 k = c._k0
316 n = (log(m1) - log(m2)) \
317 / (log(t1) - log(t2))
318 sp = 2
320 F = m1 / (n * pow(t1, n))
322 c._aF = k * E.a * F
323 c._n = n
324 c._1_n = _1_0 / n
325 c._r0 = c._rdef(t0)
326 c._SP = sp
328 return c
330 def toStr(self, prec=8, **name): # PYCHOK expected
331 '''Return this conic as a string.
333 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
334 @kwarg name: Overriding C{B{name}=NN} (C{str}) or C{None} to
335 exclude this conic's name.
337 @return: Conic attributes (C{str}).
338 '''
339 a = [_lat0_, _lon0_, _par1_, _par2_, _E0_, _N0_, _k0_, _SP_]
340 if self._SP == 1:
341 _ = a.pop(a.index(_par2_))
342 return self._instr(datum=self.datum, prec=prec, *a, **name)
344 def _dup2(self, c):
345 '''(INTERNAL) Copy this conic to C{c}.
347 @arg c: Duplicate (L{Conic}).
348 '''
349 _update_all(c)
351 c._auth = self._auth
352 c._datum = self._datum
354 c._e = self._e
355 c._E0 = self._E0
356 c._k0 = self._k0
357 c._N0 = self._N0
358 c._SP = self._SP
360 c._par1 = self._par1
361 c._par2 = self._par2
362 c._phi0 = self._phi0
363 c._lam0 = self._lam0
364 c._opt3 = self._opt3
366 c._aF = self._aF
367 c._n = self._n
368 c._1_n = self._1_n
369 c._r0 = self._r0
371 def _mdef(self, a):
372 '''(INTERNAL) Compute m(a).
373 '''
374 s, c = sincos2(a)
375 s = _1_0 - (s * self._e)**2
376 return (c / sqrt(s)) if s > EPS02 else _0_0
378 def _pdef(self, a):
379 '''(INTERNAL) Compute p(a).
380 '''
381 s = self._e * sin(a)
382 return pow((_1_0 - s) / (_1_0 + s), self._e * _0_5)
384 def _rdef(self, t):
385 '''(INTERNAL) Compute r(t).
386 '''
387 return self._aF * pow(t, self._n)
389 def _tdef(self, a):
390 '''(INTERNAL) Compute t(lat).
391 '''
392 return max(_0_0, tanPI_2_2(-a) / self._pdef(a))
394 def _xdef(self, t_x):
395 '''(INTERNAL) Compute x(t_x).
396 '''
397 return PI_2 - atan(t_x) * _2_0 # XXX + self._phi0
400Conic._name = typename(Conic)
403class Conics(_NamedEnum):
404 '''(INTERNAL) L{Conic} registry, I{must} be a sub-class
405 to accommodate the L{_LazyNamedEnumItem} properties.
406 '''
407 def _Lazy(self, lat, lon, datum_name, *args, **kwds):
408 '''(INTERNAL) Instantiate the L{Conic}.
409 '''
410 ll0 = _LLEB(lat, lon, datum=Datums.get(datum_name))
411 return Conic(ll0, *args, **kwds)
413Conics = Conics(Conic) # PYCHOK singleton
414'''Some pre-defined L{Conic}s, all I{lazily} instantiated.'''
415Conics._assert( # <https://SpatialReference.org/ref/epsg/...>
416# AsLb = _lazy('AsLb', _F(-14.2666667), _F(170), _NAD27_, _0_0, _0_0,
417# E0=_F(500000), N0=_0_0, auth='EPSG:2155'), # American Samoa ... SP=1 !
418 Be08Lb = _lazy('Be08Lb', _F(50.797815), _F(4.35921583333333), _GRS80_, _F(49.8333333333333), _F(51.1666666666667),
419 E0=_F(649328.0), N0=_F(665262.0), auth='EPSG:3812'), # ETRS89 / Belgian Lambert 2008
420 Be72Lb = _lazy('Be72Lb', _90_0, _F(4.36748666666667), _ED50_, _F(51.1666672333333), _F(49.8333339),
421 E0=_F(150000.013), N0=_F(5400088.438), auth='EPSG:31370'), # BD72 / Belgian Lambert 1972
422 Fr93Lb = _lazy('Fr93Lb', _F(46.5), _F(3), _WGS84_, _F(49), _F(44),
423 E0=_F(700000), N0=_F(6600000), auth='EPSG:2154'), # RFG93, France
424 MaNLb = _lazy('MaNLb', _F(33.3), _F(-5.4), _NTF_, _F(31.73), _F(34.87),
425 E0=_F(500000), N0=_F(300000)), # Marocco
426 MxLb = _lazy('MxLb', _F(12), _F(-102), _WGS84_, _F(17.5), _F(29.5),
427 E0=_F(2500000), N0=_0_0, auth='EPSG:2155'), # Mexico
428 PyT_Lb = _lazy('PyT_Lb', _F(46.8), _F(2.33722917), _NTF_, _F(45.89893890000052), _F(47.69601440000037),
429 E0=_F(600000), N0=_F(200000), auth='Test'), # France?
430 USA_Lb = _lazy('USA_Lb', _F(23), _F(-96), _WGS84_, _F(33), _F(45),
431 E0=_0_0, N0=_0_0), # Conterminous, contiguous USA?
432 WRF_Lb = _lazy('WRF_Lb', _F(40), _F(-97), _WGS84_, _F(33), _F(45),
433 E0=_0_0, N0=_0_0, auth='EPSG:4326') # World
434)
437class LCCError(_ValueError):
438 '''Lambert Conformal Conic C{LCC} or other L{Lcc} issue.
439 '''
440 pass
443class Lcc(_NamedBase):
444 '''Lambert conformal conic East-/Northing location.
445 '''
446 _conic = Conics.WRF_Lb # Lambert projection (L{Conic})
447 _easting = _0_0 # Easting (C{float})
448 _height = 0 # height (C{meter})
449 _northing = _0_0 # Northing (C{float})
451 def __init__(self, e, n, h=0, conic=Conics.WRF_Lb, **name):
452 '''New L{Lcc} Lambert conformal conic position.
454 @arg e: Easting (C{meter}).
455 @arg n: Northing (C{meter}).
456 @kwarg h: Optional height (C{meter}).
457 @kwarg conic: Optional, the conic projection (L{Conic}).
458 @kwarg name: Optional C{B{name}=NN} (C{str}).
460 @return: The Lambert location (L{Lcc}).
462 @raise LCCError: Invalid B{C{h}} or invalid or
463 negative B{C{e}} or B{C{n}}.
465 @raise TypeError: If B{C{conic}} is not L{Conic}.
466 '''
467 if not _isin(conic, None, Lcc._conic):
468 self.conic = conic
469 self._easting = Easting(e, falsed=conic.E0 > 0, Error=LCCError)
470 self._northing = Northing(n, falsed=conic.N0 > 0, Error=LCCError)
471 if h:
472 self._height = Height(h=h, Error=LCCError)
473 if name:
474 self.name = name
476 @Property
477 def conic(self):
478 '''Get the conic projection (L{Conic}).
479 '''
480 return self._conic
482 @conic.setter # PYCHOK setter!
483 def conic(self, conic):
484 '''Set the conic projection (L{Conic}).
486 @raise TypeError: Invalid B{C{conic}}.
487 '''
488 _xinstanceof(Conic, conic=conic)
489 if conic != self._conic:
490 _update_all(self)
491 self._conic = conic
493# def dup(self, name=NN, **e_n_h_conic): # PYCHOK signature
494# '''Duplicate this location with some attributes modified.
495#
496# @kwarg e_n_h_conic: Use keyword argument C{B{e}=...}, C{B{n}=...},
497# C{B{h}=...} and/or C{B{conic}=...} to override
498# the current C{easting}, C{northing} C{height}
499# or C{conic} projection, respectively.
500# '''
501# def _args_kwds(e=None, n=None, **kwds):
502# return (e, n), kwds
503#
504# kwds = _xkwds(e_n_h_conic, e=self.easting, n=self.northing,
505# h=self.height, conic=self.conic,
506# name=self._name__(name))
507# args, kwds = _args_kwds(**kwds)
508# return type(self)(*args, **kwds) # .classof
510 @Property_RO
511 def easting(self):
512 '''Get the easting (C{meter}).
513 '''
514 return self._easting
516 @Property_RO
517 def height(self):
518 '''Get the height (C{meter}).
519 '''
520 return self._height
522 @Property_RO
523 def latlon(self):
524 '''Get the lat- and longitude in C{degrees} (L{LatLon2Tuple}).
525 '''
526 ll = self.toLatLon(LatLon=None, datum=None)
527 return LatLon2Tuple(ll.lat, ll.lon, name=self.name)
529 @Property_RO
530 def latlonheight(self):
531 '''Get the lat-, longitude and height (L{LatLon3Tuple}C{(lat, lon, height)}).
532 '''
533 return self.latlon.to3Tuple(self.height)
535 @Property_RO
536 def latlonheightdatum(self):
537 '''Get the lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}).
538 '''
539 return self.latlonheight.to4Tuple(self.conic.datum)
541 @Property_RO
542 def northing(self):
543 '''Get the northing (C{meter}).
544 '''
545 return self._northing
547 @Property_RO
548 def philam(self):
549 '''Get the lat- and longitude in C{radians} (L{PhiLam2Tuple}).
550 '''
551 return PhiLam2Tuple(radians(self.latlon.lat),
552 radians(self.latlon.lon), name=self.name)
554 @Property_RO
555 def philamheight(self):
556 '''Get the lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
557 '''
558 return self.philam.to3Tuple(self.height)
560 @Property_RO
561 def philamheightdatum(self):
562 '''Get the lat-, longitude in C{radians} with height and datum (L{PhiLam4Tuple}C{(phi, lam, height, datum)}).
563 '''
564 return self.philamheight.to4Tuple(self.datum)
566 @deprecated_method
567 def to3lld(self, datum=None): # PYCHOK no cover
568 '''DEPRECATED, use method C{toLatLon}.
570 @kwarg datum: Optional datum to use, otherwise use this
571 B{C{Lcc}}'s conic.datum (C{Datum}).
573 @return: A L{LatLonDatum3Tuple}C{(lat, lon, datum)}.
575 @raise TypeError: If B{C{datum}} is not ellipsoidal.
576 '''
577 if _isin(datum, None, self.conic.datum):
578 r = LatLonDatum3Tuple(self.latlon.lat,
579 self.latlon.lon,
580 self.conic.datum, name=self.name)
581 else:
582 r = self.toLatLon(LatLon=None, datum=datum)
583 r = LatLonDatum3Tuple(r.lat, r.lon, r.datum, name=r.name)
584 return r
586 def toLatLon(self, LatLon=None, datum=None, height=None, **LatLon_kwds):
587 '''Convert this L{Lcc} to an (ellipsoidal) geodetic point.
589 @kwarg LatLon: Optional, ellipsoidal class to return the geodetic
590 point (C{LatLon}) or C{None}.
591 @kwarg datum: Optional datum to use, otherwise use this B{C{Lcc}}'s
592 conic.datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or
593 L{a_f2Tuple}).
594 @kwarg height: Optional height for the point, overriding the default height
595 (C{meter}).
596 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments,
597 ignored if C{B{LatLon} is None}.
599 @return: The point (B{C{LatLon}}) or if C{B{LatLon} is None}, a
600 L{LatLon4Tuple}C{(lat, lon, height, datum)}.
602 @raise TypeError: If B{C{LatLon}} or B{C{datum}} is not ellipsoidal or
603 not valid.
604 '''
605 if LatLon:
606 _xsubclassof(_LLEB, LatLon=LatLon)
608 c = self.conic
609 if not _isin(datum, None, self.conic.datum):
610 c = c.toDatum(datum)
612 e = self.easting - c._E0
613 n = c._r0 - self.northing + c._N0
615 r_ = copysign0(hypot(e, n), c._n)
616 t_ = pow(r_ / c._aF, c._1_n)
618 x = c._xdef(t_) # XXX c._lam0
619 for self._iteration in range(10): # max 4 trips
620 p, x = x, c._xdef(t_ * c._pdef(x))
621 if fabs(x - p) < 1e-9: # XXX EPS too small?
622 break
623 lat = degrees90(x)
624 lon = degrees180((atan1(e, n) + c._opt3) * c._1_n + c._lam0)
626 h = _heigHt(self, height)
627 return _LL4Tuple(lat, lon, h, c.datum, LatLon, LatLon_kwds,
628 inst=self, name=self.name)
630 def toRepr(self, prec=0, fmt=Fmt.SQUARE, sep=_COMMASPACE_, m=_m_, C=False, **unused): # PYCHOK expected
631 '''Return a string representation of this L{Lcc} position.
633 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
634 @kwarg fmt: Enclosing backets format (C{str}).
635 @kwarg sep: Optional separator between name:values (C{str}).
636 @kwarg m: Optional unit of the height, default meter (C{str}).
637 @kwarg C: Optionally, include name of conic and datum (C{bool}).
639 @return: This Lcc as "[E:meter, N:meter, H:m, C:Conic.Datum]"
640 (C{str}).
641 '''
642 t, T = _fstrENH2(self, prec, m)
643 if C:
644 t += self.conic.name2,
645 T += _C_,
646 return _xzipairs(T, t, sep=sep, fmt=fmt)
648 def toStr(self, prec=0, sep=_SPACE_, m=_m_): # PYCHOK expected
649 '''Return a string representation of this L{Lcc} position.
651 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
652 @kwarg sep: Optional separator to join (C{str}) or C{None}
653 to return an unjoined C{tuple} of C{str}s.
654 @kwarg m: Optional height units, default C{meter} (C{str}).
656 @return: This Lcc as I{"easting nothing"} in C{meter} plus
657 I{" height"} suffixed with B{C{m}} if height is
658 non-zero (C{str}).
659 '''
660 t, _ = _fstrENH2(self, prec, m)
661 return t if sep is None else sep.join(t)
664def toLcc(latlon, conic=Conics.WRF_Lb, height=None, Lcc=Lcc,
665 **name_Lcc_kwds):
666 '''Convert an (ellipsoidal) geodetic point to a I{Lambert} location.
668 @arg latlon: Ellipsoidal point (C{LatLon}).
669 @kwarg conic: Optional Lambert projection to use (L{Conic}).
670 @kwarg height: Optional height for the point, overriding the
671 default height (C{meter}).
672 @kwarg Lcc: Class to return the I{Lambert} location (L{Lcc}).
673 @kwarg name_Lcc_kwds: Optional C{B{name}=NN} (C{str}) and optionally,
674 additional B{C{Lcc}} keyword arguments, ignored if B{C{Lcc}
675 is None}.
677 @return: The I{Lambert} location (L{Lcc}) or if C{B{Lcc} is None},
678 an L{EasNor3Tuple}C{(easting, northing, height)}.
680 @raise TypeError: If B{C{latlon}} is not ellipsoidal.
681 '''
682 _xinstanceof(_LLEB, latlon=latlon)
683 name, Lcc_kwds = _name2__(name_Lcc_kwds)
685 a, b = latlon.philam
686 c = conic.toDatum(latlon.datum)
688 t = c._n * (b - c._lam0) - c._opt3
689 st, ct = sincos2(t)
691 r = c._rdef(c._tdef(a))
692 e = c._E0 + r * st
693 n = c._N0 + c._r0 - r * ct
695 h = _heigHt(latlon, height)
696 r = EasNor3Tuple(e, n, h) if Lcc is None else \
697 Lcc(e, n, h=h, conic=c, **Lcc_kwds)
698 return _xnamed(r, name) if name else r
701if __name__ == _DMAIN_:
702 # __doc__ of this file, force all into registry
703 from pygeodesy.internals import _pregistry
704 _pregistry(Conics)
706# **) MIT License
707#
708# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.
709#
710# Permission is hereby granted, free of charge, to any person obtaining a
711# copy of this software and associated documentation files (the "Software"),
712# to deal in the Software without restriction, including without limitation
713# the rights to use, copy, modify, merge, publish, distribute, sublicense,
714# and/or sell copies of the Software, and to permit persons to whom the
715# Software is furnished to do so, subject to the following conditions:
716#
717# The above copyright notice and this permission notice shall be included
718# in all copies or substantial portions of the Software.
719#
720# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
721# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
722# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
723# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
724# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
725# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
726# OTHER DEALINGS IN THE SOFTWARE.