def _zeta3(self, snu, cnu, dnu, snv, cnv, dnv): ''' @return: 3-Tuple C{(taup, lambda, d2)}. @see: C{void TMExact::zeta(real /*u*/, real snu, real cnu, real dnu, real /*v*/, real snv, real cnv, real dnv, real &taup, real &lam)} ''' e = self._e # Lee 54.17 but write # atanh(snu * dnv) = asinh(snu * dnv / sqrt(cnu^2 + _mv * snu^2 * snv^2)) # atanh(_e * snu / dnv) = asinh(_e * snu / sqrt(_mu * cnu^2 + _mv * cnv^2)) d1 = cnu**2 + self._mv * (snu * snv)**2 d2 = self._mu * cnu**2 + self._mv * cnv**2 # Overflow value s.t. atan(overflow) = pi/2 t1 = t2 = copysign(_OVERFLOW, snu) if d1 > 0: t1 = snu * dnv / sqrt(d1) if d2 > 0: t2 = sinh(e * asinh(e * snu / sqrt(d2))) # psi = asinh(t1) - asinh(t2) # taup = sinh(psi) taup = t1 * hypot1(t2) - t2 * hypot1(t1) lam = (atan2( dnu * snv, cnu * cnv) - e * atan2(e * cnu * snv, dnu * cnv)) if (d1 > 0 and d2 > 0) else 0 return taup, lam, d2
def _r3(a, f): '''(INTERNAL) Reduced cos, sin, tan. ''' t = (1 - f) * tan(radians(a)) c = 1 / hypot1(t) s = t * c return c, s, t
def _zetaInv(self, taup, lam): '''Invert C{zeta} using Newton's method. @return: 2-Tuple C{(u, v)}. @see: C{void TMExact::zetainv(real taup, real lam, real &u, real &v)}. @raise EllipticError: No convergence. ''' psi = asinh(taup) sca = 1.0 / hypot1(taup) u, v, trip = self._zetaInv0(psi, lam) if not trip: stol2 = _TOL_10 / max(psi, 1.0)**2 U, V = Fsum(u), Fsum(v) # min iterations = 2, max = 6, mean = 4.0 for _ in range(self._trips_): # GEOGRAPHICLIB_PANIC snu, cnu, dnu = self._Eu.sncndn(u) snv, cnv, dnv = self._Ev.sncndn(v) T, L, _ = self._zeta3( snu, cnu, dnu, snv, cnv, dnv) dw, dv = self._zetaDwd(snu, cnu, dnu, snv, cnv, dnv) T = (taup - T) * sca L -= lam u, du = U.fsum2_(T * dw, L * dv) v, dv = V.fsum2_(T * dv, -L * dw) if trip: break trip = (du**2 + dv**2) < stol2 else: raise EllipticError('no %s convergence' % ('zetaInv',)) return u, v
def _zetaInv(self, taup, lam): '''(INTERNAL) Invert C{zeta} using Newton's method. @return: 2-Tuple C{(u, v)}. @see: C{void TMExact::zetainv(real taup, real lam, real &u, real &v)}. @raise EllipticError: No convergence. ''' psi = asinh(taup) sca = 1.0 / hypot1(taup) u, v, trip = self._zetaInv0(psi, lam) if trip: self._iteration = 0 else: stol2 = _TOL_10 / max(psi**2, 1.0) U, V = Fsum(u), Fsum(v) # min iterations = 2, max = 6, mean = 4.0 for self._iteration in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC sncndn6 = self._sncndn6(u, v) T, L, _ = self._zeta3( *sncndn6) dw, dv = self._zetaDwd(*sncndn6) T = (taup - T) * sca L -= lam u, du = U.fsum2_(T * dw, L * dv) v, dv = V.fsum2_(T * dv, -L * dw) if trip: break trip = hypot2(du, dv) < stol2 else: t = unstr(self._zetaInv.__name__, taup, lam) raise EllipticError(_no_convergence_, txt=t) return u, v
def _cstxif3(self, ta): '''(INTERNAL) Get 3-tuple C{(cos, sin, tan)} of M{xi(ta)}. ''' t = self._txif(ta) c = _1_0 / hypot1(t) s = c * t return c, s, t
def sncndn(self, x): # PYCHOK x not used? '''The Jacobi elliptic function. @arg x: The argument (C{float}). @return: An L{Elliptic3Tuple}C{(sn, cn, dn)} with C{*n}C{(}B{C{x}}C{, k}C{)}. @raise EllipticError: No convergence. ''' # Bulirsch's sncndn routine, p 89. mc = self.kp2 if mc: # never negative ... if mc < 0: # PYCHOK no cover d = _1_0 - mc mc = neg(mc / d) # /= -d chokes PyChecker d = sqrt(d) x *= d else: d = 0 a, c, mn = _1_0, 0, [] for self._iteration in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC # This converges quadratically, max 6 trips mc = sqrt(mc) mn.append((a, mc)) c = (a + mc) * _0_5 if abs(a - mc) <= (_TolJAC * a): break mc *= a a = c else: raise _convergenceError(self.sncndn, x) x *= c dn = 1 sn, cn = sincos2(x) if sn: a = cn / sn c *= a while mn: m, n = mn.pop() a *= c c *= dn dn = (n + a) / (m + a) a = c / m sn = copysign(_1_0 / hypot1(c), sn) cn = c * sn if d: # PYCHOK no cover cn, dn = dn, cn sn = sn / d # /= d chokes PyChecker else: self._iteration = 0 sn = tanh(x) cn = dn = _1_0 / cosh(x) r = Elliptic3Tuple(sn, cn, dn) r._iteration = self._iteration return r
def forward(self, lat, lon, lon0=0, name=NN): '''Convert a geodetic location to east- and northing. @arg lat: Latitude of the location (C{degrees}). @arg lon: Longitude of the location (C{degrees}). @kwarg lon0: Optional central meridian longitude (C{degrees}). @kwarg name: Optional name for the location (C{str}). @return: An L{Albers7Tuple}C{(x, y, lat, lon, gamma, scale, datum)}. @note: The origin latitude is returned by C{property lat0}. No false easting or northing is added. The value of B{C{lat}} should be in the range C{[-90..90] degrees}. The returned values C{x} and C{y} will be large but finite for points projecting to infinity, i.e. one or both of the poles. ''' E = self.datum.ellipsoid s = self._sign k0 = self._k0 n0 = self._n0 nrho0 = self._nrho0 txi0 = self._txi0 sa, ca = sincos2d(_Lat_(lat) * s) ca = max(_EPSX, ca) ta = sa / ca _, sxi, txi = self._cstxif3(ta) dq = self._qZ * _Dsn(txi, txi0, sxi, self._sxi0) * (txi - txi0) drho = -E.a * dq / (sqrt(self._m02 - n0 * dq) + self._m0) lon = _Lon_(lon) if lon0: lon, _ = _diff182(_Lon_(lon0, name=_lon0_), lon) b = radians(lon) th = self._k02n0 * b sth, cth = sincos2(th) # XXX sin, cos if n0: x = sth / n0 y = (1 - cth if cth < 0 else sth**2 / (1 + cth)) / n0 else: x = self._k02 * b y = 0 t = nrho0 + n0 * drho x = t * x / k0 y = s * (nrho0 * y - drho * cth) / k0 g = degrees360(s * th) if t: k0 *= t * hypot1(E.b_a * ta) / E.a t = Albers7Tuple(x, y, lat, lon, g, k0, self.datum) return _xnamed(t, name or self.name)
def sncndn(self, x): # PYCHOK x not used? '''The Jacobi elliptic function. @param x: The argument (C{float}). @return: An L{Elliptic3Tuple}C{(sn, cn, dn)} with C{sn(B{x}, k), cn(B{x}, k), dn(B{x}, k)}. @raise EllipticError: No convergence. ''' # Bulirsch's sncndn routine, p 89. mc = self.kp2 if mc: # never negative ... if mc < 0: # PYCHOK no cover d = 1.0 - mc mc = -mc / d # /= -d chokes PyChecker d = sqrt(d) x *= d else: d = 0 a, c, mn = 1.0, 0, [] for _ in range(self._trips_): # GEOGRAPHICLIB_PANIC # This converges quadratically, max 6 trips mc = sqrt(mc) mn.append((a, mc)) c = (a + mc) * 0.5 if abs(a - mc) <= (_TolJAC * a): break mc *= a a = c else: raise EllipticError('no %s%r convergence' % ('sncndn', (x,))) x *= c dn = 1 sn, cn = sincos2(x) if sn: a = cn / sn c *= a while mn: m, n = mn.pop() a *= c c *= dn dn = (n + a) / (m + a) a = c / m sn = copysign(1.0 / hypot1(c), sn) cn = c * sn if d: # PYCHOK no cover cn, dn = dn, cn sn = sn / d # /= d chokes PyChecker else: sn = tanh(x) cn = dn = 1.0 / cosh(x) return Elliptic3Tuple(sn, cn, dn)
def to3llh(self, datum=Datums.WGS84): '''Convert this (geocentric) Cartesian (x/y/z) point to (ellipsoidal, geodetic) lat-, longitude and height on the given datum. Uses B. R. Bowring’s formulation for μm precision in concise form: U{'The accuracy of geodetic latitude and height equations' <https://www.ResearchGate.net/publication/ 233668213_The_Accuracy_of_Geodetic_Latitude_and_Height_Equations>}, Survey Review, Vol 28, 218, Oct 1985. See also Ralph M. Toms U{'An Efficient Algorithm for Geocentric to Geodetic Coordinate Conversion'<https://www.OSTI.gov/scitech/biblio/110235>}, Sept 1995 and U{'An Improved Algorithm for Geocentric to Geodetic Coordinate Conversion'<https://www.OSTI.gov/scitech/servlets/purl/231228>}, Apr 1996, from Lawrence Livermore National Laboratory. @keyword datum: Optional datum to use (L{Datum}). @return: A L{LatLon3Tuple}C{(lat, lon, height)}. ''' E = datum.ellipsoid x, y, z = self.to3xyz() p = hypot(x, y) # distance from minor axis r = hypot(p, z) # polar radius if min(p, r) > EPS: # parametric latitude (Bowring eqn 17, replaced) t = (E.b * z) / (E.a * p) * (1 + E.e22 * E.b / r) c = 1 / hypot1(t) s = t * c # geodetic latitude (Bowring eqn 18) a = atan2(z + E.e22 * E.b * s**3, p - E.e2 * E.a * c**3) b = atan2(y, x) # ... and longitude # height above ellipsoid (Bowring eqn 7) sa, ca = sincos2(a) # r = E.a / E.e2s(sa) # length of normal terminated by minor axis # h = p * ca + z * sa - (E.a * E.a / r) h = fsum_(p * ca, z * sa, -E.a * E.e2s(sa)) a, b = degrees90(a), degrees180(b) # see <https://GIS.StackExchange.com/questions/28446> elif p > EPS: # latitude arbitrarily zero a, b, h = 0.0, degrees180(atan2(y, x)), p - E.a else: # polar latitude, longitude arbitrarily zero a, b, h = copysign(90.0, z), 0.0, abs(z) - E.b return self._xnamed(LatLon3Tuple(a, b, h))
def sncndn(self, x): '''The Jacobi elliptic function. @param x: The argument (C{float}). @return: 3-Tuple C{(sn(x, k), cn(x, k), dn(x, k))}. ''' # Bulirsch's sncndn routine, p 89. mc = self._kp2 if mc: if mc < 0: d = 1.0 - mc mc /= -d d = sqrt(d) x *= d else: d = 0 a, c, mn = 1.0, 0, [] for _ in range(self._trips_): # GEOGRAPHICLIB_PANIC # This converges quadratically, max 6 trips mc = sqrt(mc) mn.append((a, mc)) c = (a + mc) * 0.5 if not abs(a - mc) > (_tolJAC * a): break mc *= a a = c else: raise EllipticError('no %s convergence' % ('sncndn', )) x *= c dn = 1 sn, cn = sincos2(x) if sn: a = cn / sn c *= a while mn: m, n = mn.pop() a *= c c *= dn dn = (n + a) / (m + a) a = c / m sn = copysign(1.0 / hypot1(c), sn) cn = c * sn if d: cn, dn = dn, cn sn /= d else: sn = tanh(x) cn = dn = 1.0 / cosh(x) return sn, cn, dn
def _scale(E, rho, tau): # compute the point scale factor, ala Karney t = hypot1(tau) return (rho / E.a) * t * sqrt(E.e12 + E.e2 / t**2)
def toUps8(latlon, lon=None, datum=None, Ups=Ups, pole='', falsed=True, strict=True, name=''): '''Convert a lat-/longitude point to a UPS coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @keyword lon: Optional longitude (C{degrees}) or C{None} if B{C{latlon}} is a C{LatLon}. @keyword datum: Optional datum for this UPS coordinate, overriding B{C{latlon}}'s datum (C{Datum}). @keyword Ups: Optional (sub-)class to return the UPS coordinate (L{Ups}) or C{None}. @keyword pole: Optional top/center of (stereographic) projection (C{str}, C{'N[orth]'} or C{'S[outh]'}). @keyword falsed: False both easting and northing (C{bool}). @keyword strict: Restrict B{C{lat}} to UPS ranges (C{bool}). @keyword name: Optional B{C{Ups}} name (C{str}). @return: The UPS coordinate (B{C{Ups}}) or a L{UtmUps8Tuple}C{(zone, hemipole, easting, northing, band, datum, convergence, scale)} if B{C{Ups}} is C{None}. The C{hemipole} is the C{'N'|'S'} pole, the UPS projection top/center. @raise RangeError: If B{C{strict}} and B{C{lat}} outside the valid UPS bands or if B{C{lat}} or B{C{lon}} outside the valid range and L{rangerrors} set to C{True}. @raise TypeError: If B{C{latlon}} is not ellipsoidal. @raise ValueError: If B{C{lon}} value is missing or if B{C{latlon}} is invalid. @see: Karney's C++ class U{UPS <https://GeographicLib.SourceForge.io/html/classGeographicLib_1_1UPS.html>}. ''' lat, lon, d, name = _to4lldn(latlon, lon, datum, name) z, B, p, lat, lon = upsZoneBand5(lat, lon, strict=strict) # PYCHOK UtmUpsLatLon5Tuple E = d.ellipsoid p = str(pole or p)[:1].upper() N = p == 'N' # is north a = lat if N else -lat A = abs(a - 90) < _TOL # at pole t = tan(radians(a)) T = E.es_taupf(t) r = hypot1(T) + abs(T) if T >= 0: r = 0 if A else 1 / r k0 = getattr(Ups, '_scale0', _K0) # Ups is class or None r *= 2 * k0 * E.a / E.es_c k = k0 if A else _scale(E, r, t) c = lon # [-180, 180) from .upsZoneBand5 x, y = sincos2d(c) x *= r y *= r if N: y = -y else: c = -c if falsed: x += _Falsing y += _Falsing if Ups is None: r = UtmUps8Tuple(z, p, x, y, B, d, c, k) else: if z != _UPS_ZONE and not strict: z = _UPS_ZONE # ignore UTM zone r = Ups(z, p, x, y, band=B, datum=d, falsed=falsed, convergence=c, scale=k) r._hemisphere = _hemi(lat) if isinstance(latlon, _LLEB) and d is latlon.datum: r._latlon_to(latlon, falsed) # XXX weakref(latlon)? return _xnamed(r, name)
def toUtm8(latlon, lon=None, datum=None, Utm=Utm, falsed=True, name=NN, zone=None, **cmoff): '''Convert a lat-/longitude point to a UTM coordinate. @arg latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @kwarg lon: Optional longitude (C{degrees}) or C{None}. @kwarg datum: Optional datum for this UTM coordinate, overriding B{C{latlon}}'s datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}). @kwarg Utm: Optional class to return the UTM coordinate (L{Utm}) or C{None}. @kwarg falsed: False both easting and northing (C{bool}). @kwarg name: Optional B{C{Utm}} name (C{str}). @kwarg zone: Optional UTM zone to enforce (C{int} or C{str}). @kwarg cmoff: DEPRECATED, use B{C{falsed}}. Offset longitude from the zone's central meridian (C{bool}). @return: The UTM coordinate (B{C{Utm}}) or if B{C{Utm}} is C{None} or not B{C{falsed}}, a L{UtmUps8Tuple}C{(zone, hemipole, easting, northing, band, datum, convergence, scale)}. The C{hemipole} is the C{'N'|'S'} hemisphere. @raise RangeError: If B{C{lat}} outside the valid UTM bands or if B{C{lat}} or B{C{lon}} outside the valid range and L{rangerrors} set to C{True}. @raise TypeError: Invalid B{C{datum}} or B{C{latlon}} not ellipsoidal. @raise UTMError: Invalid B{C{zone}}. @raise ValueError: If B{C{lon}} value is missing or if B{C{latlon}} is invalid. @note: Implements Karney’s method, using 8-th order Krüger series, giving results accurate to 5 nm (or better) for distances up to 3900 km from the central meridian. @example: >>> p = LatLon(48.8582, 2.2945) # 31 N 448251.8 5411932.7 >>> u = toUtm(p) # 31 N 448252 5411933 >>> p = LatLon(13.4125, 103.8667) # 48 N 377302.4 1483034.8 >>> u = toUtm(p) # 48 N 377302 1483035 ''' z, B, lat, lon, d, f, name = _to7zBlldfn(latlon, lon, datum, falsed, name, zone, UTMError, **cmoff) d = _ellipsoidal_datum(d, name=name) E = d.ellipsoid a, b = radians(lat), radians(lon) # easting, northing: Karney 2011 Eq 7-14, 29, 35 sb, cb = sincos2(b) T = tan(a) T12 = hypot1(T) S = sinh(E.e * atanh(E.e * T / T12)) T_ = T * hypot1(S) - S * T12 H = hypot(T_, cb) y = atan2(T_, cb) # ξ' ksi x = asinh(sb / H) # η' eta A0 = E.A * getattr(Utm, '_scale0', _K0) # Utm is class or None K = _Kseries(E.AlphaKs, x, y) # Krüger series y = K.ys(y) * A0 # ξ x = K.xs(x) * A0 # η # convergence: Karney 2011 Eq 23, 24 p_ = K.ps(1) q_ = K.qs(0) c = degrees(atan(T_ / hypot1(T_) * tan(b)) + atan2(q_, p_)) # scale: Karney 2011 Eq 25 k = E.e2s(sin(a)) * T12 / H * (A0 / E.a * hypot(p_, q_)) return _toXtm8(Utm, z, lat, x, y, B, d, c, k, f, name, latlon, EPS)
def toLatLon(self, LatLon=None, eps=EPS, unfalse=True, **LatLon_kwds): '''Convert this UTM coordinate to an (ellipsoidal) geodetic point. @kwarg LatLon: Optional, ellipsoidal class to return the geodetic point (C{LatLon}) or C{None}. @kwarg eps: Optional convergence limit, L{EPS} or above (C{float}). @kwarg unfalse: Unfalse B{C{easting}} and B{C{northing}} if falsed (C{bool}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: This UTM coordinate (B{C{LatLon}}) or if B{C{LatLon}} is C{None}, a L{LatLonDatum5Tuple}C{(lat, lon, datum, convergence, scale)}. @raise TypeError: If B{C{LatLon}} is not ellipsoidal. @raise UTMError: Invalid meridional radius or H-value. @example: >>> u = Utm(31, 'N', 448251.795, 5411932.678) >>> from pygeodesy import ellipsoidalVincenty as eV >>> ll = u.toLatLon(eV.LatLon) # 48°51′29.52″N, 002°17′40.20″E ''' if eps < EPS: eps = EPS # less doesn't converge if self._latlon and self._latlon_args == (eps, unfalse): return self._latlon5(LatLon) E = self.datum.ellipsoid # XXX vs LatLon.datum.ellipsoid x, y = self.to2en(falsed=not unfalse) # from Karney 2011 Eq 15-22, 36 A0 = self.scale0 * E.A if A0 < EPS: raise self._Error(meridional=A0) x /= A0 # η eta y /= A0 # ξ ksi K = _Kseries(E.BetaKs, x, y) # Krüger series y = -K.ys(-y) # ξ' x = -K.xs(-x) # η' shx = sinh(x) sy, cy = sincos2(y) H = hypot(shx, cy) if H < EPS: raise self._Error(H=H) T = t0 = sy / H # τʹ S = Fsum(T) q = 1.0 / E.e12 P = 7 # -/+ toggle trips d = 1.0 + eps while abs(d) > eps and P > 0: p = -d # previous d, toggled h = hypot1(T) s = sinh(E.e * atanh(E.e * T / h)) t = T * hypot1(s) - s * h d = (t0 - t) / hypot1(t) * ((q + T**2) / h) T, d = S.fsum2_(d) # τi, (τi - τi-1) if d == p: # catch -/+ toggling of d P -= 1 # else: # P = 0 a = atan(T) # lat b = atan2(shx, cy) if unfalse and self.falsed: b += radians(_cmlon(self.zone)) ll = _LLEB(degrees90(a), degrees180(b), datum=self.datum, name=self.name) # convergence: Karney 2011 Eq 26, 27 p = -K.ps(-1) q = K.qs(0) ll._convergence = degrees(atan(tan(y) * tanh(x)) + atan2(q, p)) # scale: Karney 2011 Eq 28 ll._scale = E.e2s(sin(a)) * hypot1(T) * H * (A0 / E.a / hypot(p, q)) self._latlon_to(ll, eps, unfalse) return self._latlon5(LatLon, **LatLon_kwds)
def reverse(self, xyz, y=None, z=None, **no_M): # PYCHOK unused M '''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)} transcribed from I{Chris Veness}' U{JavaScript <https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html>}. Uses B. R. Bowring’s formulation for μm precision in concise form: U{'The accuracy of geodetic latitude and height equations' <https://www.ResearchGate.net/publication/ 233668213_The_Accuracy_of_Geodetic_Latitude_and_Height_Equations>}, Survey Review, Vol 28, 218, Oct 1985. @arg xyz: Either an L{Ecef9Tuple}, an C{(x, y, z)} 3-tuple or C{scalar} ECEF C{x} coordinate in C{meter}. @kwarg y: ECEF C{y} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{z}}. @kwarg z: ECEF C{z} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{y}}. @kwarg no_M: Rotation matrix C{M} not available. @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with geodetic coordinates C{(lat, lon, height)} for the given geocentric ones C{(x, y, z)}, case C{C}, L{EcefMatrix} C{M} always C{None} and C{datum} if available. @raise EcefError: If B{C{xyz}} not L{Ecef9Tuple} or C{scalar} C{x} or B{C{y}} and/or B{C{z}} not C{scalar} for C{scalar} B{C{xyz}}. @see: Ralph M. Toms U{'An Efficient Algorithm for Geocentric to Geodetic Coordinate Conversion'<https://www.OSTI.gov/scitech/biblio/110235>}, Sept 1995 and U{'An Improved Algorithm for Geocentric to Geodetic Coordinate Conversion'<https://www.OSTI.gov/scitech/servlets/purl/231228>}, Apr 1996, both from Lawrence Livermore National Laboratory (LLNL). ''' x, y, z, name = _xyzn4(xyz, y, z, Error=EcefError) E = self.ellipsoid p = hypot(x, y) # distance from minor axis r = hypot(p, z) # polar radius if min(p, r) > EPS: # parametric latitude (Bowring eqn 17, replaced) t = (E.b * z) / (E.a * p) * (1 + E.e22 * E.b / r) c = 1 / hypot1(t) s = t * c # geodetic latitude (Bowring eqn 18) a = atan2(z + E.e22 * E.b * s**3, p - E.e2 * E.a * c**3) b = atan2(y, x) # ... and longitude # height above ellipsoid (Bowring eqn 7) sa, ca = sincos2(a) # r = E.a / E.e2s(sa) # length of normal terminated by minor axis # h = p * ca + z * sa - (E.a * E.a / r) h = fsum_(p * ca, z * sa, -E.a * E.e2s(sa)) C, lat, lon = 1, degrees90(a), degrees180(b) # see <https://GIS.StackExchange.com/questions/28446> elif p > EPS: # lat arbitrarily zero C, lat, lon, h = 2, 0.0, degrees180(atan2(y, x)), p - E.a else: # polar lat, lon arbitrarily zero C, lat, lon, h = 3, copysign(90.0, z), 0.0, abs(z) - E.b r = Ecef9Tuple(x, y, z, lat, lon, h, C, None, self.datum) return self._xnamed(r, name)
def _azik(self, t, ta): '''(INTERNAL) Compute the azimuthal scale C{_Ks(k0=k0)}. ''' E = self.datum.ellipsoid return _Ks(k=self._k0 * t * hypot1(E.b_a * ta) / E.a)
def __init__(self, sa1, ca1, sa2, ca2, k, datum, name): '''(INTERNAL) New C{AlbersEqualArea...} instance. ''' if datum not in (None, self._datum): self._datum = _ellipsoidal_datum(datum, name=name) if name: self.name = name E = self.datum.ellipsoid b_a = E.b_a # fm = 1 - E.f e2 = E.e2 e12 = E.e12 # e2m = 1 - E.e2 self._qZ = qZ = _1_0 + e12 * self._atanhee(1) self._qZa2 = qZ * E.a2 self._qx = qZ / (_2_0 * e12) c = min(ca1, ca2) if c < 0: raise AlbersError(clat1=ca1, clat2=ca2) polar = c < _EPS__2 # == 0 # determine hemisphere of tangent latitude if sa1 < 0: # and sa2 < 0: self._sign = -1 # internally, tangent latitude positive sa1, sa2 = -sa1, neg(sa2) if sa1 > sa2: # make phi1 < phi2 sa1, sa2 = sa2, sa1 ca1, ca2 = ca2, ca1 if sa1 < 0: # or sa2 < 0: raise AlbersError(slat1=sa1, slat2=sa2) # avoid singularities at poles ca1, ca2 = max(_EPS__2, ca1), max(_EPS__2, ca2) ta1, ta2 = sa1 / ca1, sa2 / ca2 par1 = abs(ta1 - ta2) < _EPS__4 # ta1 == ta2 if par1 or polar: C, ta0 = _1_0, ta2 else: s1_qZ, C = self._s1_qZ_C2(ca1, sa1, ta1, ca2, sa2, ta2) ta0 = (ta2 + ta1) * _0_5 Ta0 = Fsum(ta0) tol = _tol(_TOL0, ta0) for self._iteration in range(1, _NUMIT0): ta02 = ta0**2 sca02 = ta02 + _1_0 sca0 = sqrt(sca02) sa0 = ta0 / sca0 sa01 = sa0 + _1_0 sa02 = sa0**2 # sa0m = 1 - sa0 = 1 / (sec(a0) * (tan(a0) + sec(a0))) sa0m = _1_0 / (sca0 * (ta0 + sca0)) # scb0^2 * sa0 g = (_1_0 + (b_a * ta0)**2) * sa0 dg = e12 * sca02 * (_1_0 + 2 * ta02) + e2 D = sa0m * (_1_0 - e2 * (_1_0 + sa01 * 2 * sa0)) / (e12 * sa01) # dD/dsa0 dD = -2 * (_1_0 - e2 * sa02 * (_3_0 + 2 * sa0)) / (e12 * sa01**2) sa02_ = _1_0 - e2 * sa02 sa0m_ = sa0m / (_1_0 - e2 * sa0) BA = sa0m_ * (self._atanhx1(e2 * sa0m_**2) * e12 - e2 * sa0m) \ - sa0m**2 * e2 * (2 + (_1_0 + e2) * sa0) / (e12 * sa02_) # == B + A dAB = 2 * e2 * (2 - e2 * (_1_0 + sa02)) / (e12 * sa02_**2 * sca02) u_du = fsum_(s1_qZ * g, -D, g * BA) \ / fsum_(s1_qZ * dg, -dD, dg * BA, g * dAB) # == u/du ta0, d = Ta0.fsum2_(-u_du * (sca0 * sca02)) if abs(d) < tol: break else: raise AlbersError(iteration=_NUMIT0, txt=_no_(Fmt.convergence(tol))) self._txi0 = txi0 = self._txif(ta0) self._scxi0 = hypot1(txi0) self._sxi0 = sxi0 = txi0 / self._scxi0 self._m02 = m02 = _1_0 / (_1_0 + (b_a * ta0)**2) self._n0 = n0 = ta0 / hypot1(ta0) if polar: self._polar = True self._nrho0 = self._m0 = _0_0 else: self._m0 = sqrt(m02) # == nrho0 / E.a self._nrho0 = E.a * self._m0 # == E.a * sqrt(m02) self._k0_(_1_0 if par1 else (k * sqrt(C / (m02 + n0 * qZ * sxi0)))) self._lat0 = _Lat(lat0=self._sign * atand(ta0))
def reverse(self, x, y, lon0=0, name=NN, LatLon=None, **LatLon_kwds): '''Convert an east- and northing location to geodetic lat- and longitude. @arg x: Easting of the location (C{meter}). @arg y: Northing of the location (C{meter}). @kwarg lon0: Optional central meridian longitude (C{degrees}). @kwarg name: Optional name for the location (C{str}). @kwarg LatLon: Class to use (C{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: The geodetic (C{LatLon}) or if B{C{LatLon}} is C{None} an L{Albers7Tuple}C{(x, y, lat, lon, gamma, scale, datum)}. @note: The origin latitude is returned by C{property lat0}. No false easting or northing is added. The returned value of C{lon} is in the range C{[-180..180] degrees} and C{lat} is in the range C{[-90..90] degrees}. If the given B{C{x}} or B{C{y}} point is outside the valid projected space the nearest pole is returned. ''' x = Scalar(x, name=_x_) y = Scalar(y, name=_y_) E = self.datum.ellipsoid k0 = self._k0 n0 = self._n0 k0n0 = self._k0n0 nrho0 = self._nrho0 txi0 = self._txi0 y_ = self._sign * y nx = k0n0 * x ny = k0n0 * y_ y1 = nrho0 - ny drho = den = nrho0 + hypot(nx, y1) # 0 implies origin with polar aspect if den: drho = fsum_(x * nx, -2 * y_ * nrho0, y_ * ny) * k0 / den # dsxia = scxi0 * dsxi dsxia = -self._scxi0 * (2 * nrho0 + n0 * drho) * drho / self._qZa2 t = 1 - dsxia * (2 * txi0 + dsxia) txi = (txi0 + dsxia) / (sqrt(t) if t > _EPSX2 else _EPSX) ta = self._tanf(txi) lat = degrees(atan(ta * self._sign)) b = atan2(nx, y1) lon = degrees(b / self._k02n0 if n0 else x / (y1 * k0)) if lon0: lon += _norm180(_Lon_(lon0, name=_lon0_)) lon = _norm180(lon) if LatLon is None: g = degrees360(self._sign * b) if den: k0 *= (nrho0 + n0 * drho) * hypot1(E.b_a * ta) / E.a r = Albers7Tuple(x, y, lat, lon, g, k0, self.datum) else: kwds = _xkwds(LatLon_kwds, datum=self.datum) r = LatLon(lat, lon, **kwds) return _xnamed(r, name or self.name)