def _RD(x, y, z): '''Degenerate symmetric integral of the third kind C{_RD}. @return: C{_RD(x, y, z) = _RJ(x, y, z, z)}. @see: U{C{_RD} definition<https://DLMF.NIST.gov/19.16.E5>}. ''' # Carlson, eqs 2.28 - 2.34 m = 1.0 S = Fsum() A = fsum_(x, y, z, z, z) * 0.2 T = [A, x, y, z] Q = _Q(A, T, _tolRD) for _ in range(_TRIPS): if Q < abs(m * T[0]): # max 7 trips break t = T[3] # z0 r, s, T = _rsT(T) S.fadd_(1.0 / (m * s[2] * (t + r))) m *= 4 else: raise EllipticError('no %s convergence' % ('RD',)) S *= 3 m *= T[0] # An x = (x - A) / m y = (y - A) / m z = (x + y) / 3.0 z2 = z**2 xy = x * y return _Hf(S.fsum(), m * sqrt(T[0]), xy - 6 * z2, (xy * 3 - 8 * z2) * z, (xy - z2) * 3 * z2, xy * z2 * z)
def _RG(x, y, z=None): '''Symmetric integral of the second kind C{_RG}. @return: C{_RG(x, y, z)}. @see: U{C{_RG} definition<https://DLMF.NIST.gov/19.16.E3>} and in Carlson, eq. 1.5. ''' if z is None: # Carlson, eqs 2.36 - 2.39 a, b = sqrt(x), sqrt(y) if a < b: a, b = b, a S = Fsum(0.25 * (a + b)**2) m = -0.25 # note, negative while abs(a - b) > (_tolRG0 * a): # max 4 trips b, a = sqrt(a * b), (a + b) * 0.5 m *= 2 S.fadd_(m * (a - b)**2) return S.fsum() * PI_2 / (a + b) if not z: y, z = z, y # Carlson, eq 1.7 return fsum_(_RF(x, y, z) * z, _RD_3(x, y, z) * (x - z) * (z - y), sqrt(x * y / z)) * 0.5
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 _sigmaInv(self, xi, eta): '''Invert C{sigma} using Newton's method. @return: 2-Tuple C{(u, v)}. @see: C{void TMExact::sigmainv(real xi, real eta, real &u, real &v)}. @raise EllipticError: No convergence. ''' u, v, trip = self._sigmaInv0(xi, eta) if not trip: U, V = Fsum(u), Fsum(v) # min iterations = 2, max = 7, mean = 3.9 for _ in range(self._trips_): # GEOGRAPHICLIB_PANIC snu, cnu, dnu = self._Eu.sncndn(u) snv, cnv, dnv = self._Ev.sncndn(v) X, E, _ = self._sigma3(v, snu, cnu, dnu, snv, cnv, dnv) dw, dv = self._sigmaDwd(snu, cnu, dnu, snv, cnv, dnv) X = xi - X E -= eta u, du = U.fsum2_(X * dw, E * dv) v, dv = V.fsum2_(X * dv, -E * dw) if trip: break trip = (du**2 + dv**2) < _TOL_10 else: raise EllipticError('no %s convergence' % ('sigmaInv', )) return u, v
def centroidOf(points, wrap=True, LatLon=None): '''Determine the centroid of a polygon. @param points: The polygon points (C{LatLon}[]). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}). @keyword LatLon: Optional (sub-)class to return the centroid (L{LatLon}) or C{None}. @return: Centroid location (I{LatLon}) or as 2-tuple (C{lat, lon}) in C{degrees} if I{LatLon} is C{None}. @raise TypeError: Some I{points} are not C{LatLon}. @raise ValueError: Insufficient number of I{points} or I{points} enclose a pole or zero area. @see: U{Centroid<http://WikiPedia.org/wiki/Centroid#Of_a_polygon>} and U{Calculating The Area And Centroid Of A Polygon <http://www.Seas.UPenn.edu/~sys502/extra_materials/ Polygon%20Area%20and%20Centroid.pdf>}. ''' # setting radius=1 converts degrees to radians pts = LatLon2psxy(points, closed=True, radius=1, wrap=wrap) n = len(pts) A, X, Y = Fsum(), Fsum(), Fsum() x1, y1, _ = pts[n-1] for i in range(n): x2, y2, _ = pts[i] if wrap and i < (n - 1): _, x2 = unrollPI(x1, x2, wrap=True) t = x1 * y2 - x2 * y1 A += t X += t * (x1 + x2) Y += t * (y1 + y2) # XXX more elaborately: # t1, t2 = x1 * y2, -(x2 * y1) # A.fadd_(t1, t2) # X.fadd_(t1 * x1, t1 * x2, t2 * x1, t2 * x2) # Y.fadd_(t1 * y1, t1 * y2, t2 * y1, t2 * y2) x1, y1 = x2, y2 A = A.fsum() * 3.0 # 6.0 / 2.0 if abs(A) < EPS: raise ValueError('polar or zero area: %r' % (pts,)) Y, X = degrees90(Y.fsum() / A), degrees180(X.fsum() / A) return (Y, X) if LatLon is None else LatLon(Y, X)
def _RJ(x, y, z, p): '''Symmetric integral of the third kind C{_RJ}. @return: C{_RJ(x, y, z, p)}. @see: U{C{_RJ} definition<https://DLMF.NIST.gov/19.16.E2>}. ''' def _xyzp(x, y, z, p): return (x + p) * (y + p) * (z + p) # Carlson, eqs 2.17 - 2.25 m = m3 = 1.0 S = Fsum() D = -_xyzp(x, y, z, -p) A = fsum_(x, y, z, 2 * p) * 0.2 T = [A, x, y, z, p] Q = _Q(A, T, _tolRD) for _ in range(_TRIPS): if Q < abs(m * T[0]): # max 7 trips break _, s, T = _rsT(T) d = _xyzp(*s) e = D / (m3 * d**2) S.fadd_(_RC(1, 1 + e) / (m * d)) m *= 4 m3 *= 64 else: raise EllipticError('no %s convergence' % ('RJ',)) S *= 6 m *= T[0] # An x = (A - x) / m y = (A - y) / m z = (A - z) / m xyz = x * y * z p = -(x + y + z) * 0.5 p2 = p**2 e2 = fsum_(x * y, x * z, y * z, -3 * p2) return _Hf(S.fsum(), m * sqrt(T[0]), e2, fsum_(xyz, 2 * p * e2, 4 * p * p2), fsum_(xyz * 2, p * e2, 3 * p * p2) * p, p2 * xyz)
def fEinv(self, x): '''The inverse of the incomplete integral of the second kind. @param x: Argument (C{float}). @return: φ = 1 / E(B{C{x}}, k), such that E(φ, k) = B{C{x}}. ''' E2 = self.cE * 2.0 n = floor(x / E2 + 0.5) x -= E2 * n # x now in [-ec, ec) # linear approximation phi = PI * x / E2 # phi in [-pi/2, pi/2) P = Fsum(phi) # first order correction phi = P.fsum(-self._eps * sin(2 * phi) * 0.5) # For kp2 close to zero use asin(x/.cE) or J. P. Boyd, # Applied Math. and Computation 218, 7005-7013 (2012) # <https://DOI.org/10.1016/j.amc.2011.12.021> for _ in range(self._trips_): # GEOGRAPHICLIB_PANIC sn, cn, dn = self._sncndn3(phi) phi, e = P.fsum2_((x - self.fE(sn, cn, dn)) / dn) if abs(e) < _tolJAC: return n * PI + phi raise EllipticError('no %s convergence' % ('fEinv',))
def isenclosedBy(point, points, wrap=False): # MCCABE 15 '''Determine whether a point is enclosed by a polygon. @param point: The point (C{LatLon} or 2-tuple (lat, lon)). @param points: The polygon points (C{LatLon}[]). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}). @return: C{True} if I{point} is inside the polygon, C{False} otherwise. @raise TypeError: Some I{points} are not C{LatLon}. @raise ValueError: Insufficient number of I{points} or invalid I{point}. @see: L{sphericalNvector.LatLon.isEnclosedBy}, L{sphericalTrigonometry.LatLon.isEnclosedBy} and U{MultiDop GeogContainPt<http://GitHub.com/NASA/MultiDop>} (U{Shapiro et al. 2009, JTECH <http://journals.AMetSoc.org/doi/abs/10.1175/2009JTECHA1256.1>} and U{Potvin et al. 2012, JTECH <http://journals.AMetSoc.org/doi/abs/10.1175/JTECH-D-11-00019.1>}). ''' try: y0, x0 = point.lat, point.lon except AttributeError: try: y0, x0 = map1(float, *point[:2]) except (IndexError, TypeError, ValueError): raise ValueError('%s invalid: %r' % ('point', point)) pts = LatLon2psxy(points, closed=True, radius=None, wrap=wrap) n = len(pts) if wrap: x0, y0 = wrap180(x0), wrap90(y0) def _dxy(x1, i): x2, y2, _ = pts[i] dx, x2 = unroll180(x1, x2, wrap=i < (n - 1)) return dx, x2, y2 else: x0 = fmod(x0, 360.0) # not x0 % 360 def _dxy(x1, i): # PYCHOK expected x, y, _ = pts[i] x %= 360.0 if x < (x0 - 180): x += 360 elif x >= (x0 + 180): x -= 360 return (x - x1), x, y e = m = False s = Fsum() _, x1, y1 = _dxy(x0, n - 1) for i in range(n): dx, x2, y2 = _dxy(x1, i) # ignore duplicate and near-duplicate pts if max(abs(dx), abs(y2 - y1)) > EPS: # determine if polygon edge (x1, y1)..(x2, y2) straddles # point (lat, lon) or is on boundary, but do not count # edges on boundary as more than one crossing if abs(dx) < 180 and (x1 < x0 <= x2 or x2 < x0 <= x1): m = not m dy = (x0 - x1) * (y2 - y1) - (y0 - y1) * dx if (dy > 0 and dx >= 0) or (dy < 0 and dx <= 0): e = not e s.fadd(sin(radians(y2))) x1, y1 = x2, y2 # An odd number of meridian crossings means, the polygon # contains a pole. Assume it is the pole on the hemisphere # containing the polygon mean point and if the polygon does # contain the North Pole, flip the result. if m and s.fsum() > 0: e = not e return e
def toLatLon(self, LatLon=None, datum=Datums.WGS84): '''Convert this OSGR coordinate to an (ellipsoidal) geodetic point. I{Note formulation implemented here due to Thomas, Redfearn, etc. is as published by OS, but is inferior to Krüger as used by e.g. Karney 2011.} @keyword LatLon: Optional ellipsoidal LatLon class to use for the point (I{LatLon}). @keyword datum: Optional datum to use (I{Datum}). @return: The geodetic point (I{LatLon}) or 3-tuple (lat, lon, datum) if I{LatLon} is None. @raise TypeError: If I{LatLon} is not ellipsoidal or if I{datum} conversion failed. @example: >>> from pygeodesy import ellipsoidalVincenty as eV >>> g = Osgr(651409.903, 313177.270) >>> p = g.toLatLon(eV.LatLon) # 52°39′28.723″N, 001°42′57.787″E >>> # to obtain (historical) OSGB36 lat-/longitude point >>> p = g.toLatLon(eV.LatLon, datum=Datums.OSGB36) # 52°39′27.253″N, 001°43′04.518″E ''' if self._latlon: return self._latlon3(LatLon, datum) E = _OSGB36.ellipsoid # Airy130 a_F0 = E.a * _F0 b_F0 = E.b * _F0 e, n = self._easting, self._northing n_N0 = n - _N0 a, M = _A0, 0 sa = Fsum(a) while True: t = n_N0 - M if t < _10um: break sa.fadd(t / a_F0) a = sa.fsum() M = b_F0 * _M(E.Mabcd, a) ca, sa, ta = cos(a), sin(a), tan(a) s = E.e2s2(sa) v = a_F0 / sqrt(s) # nu r = v * E.e12 / s # rho vr = v / r # == s / E.e12 x2 = vr - 1 # η2 v3, v5, v7 = fpowers(v, 7, 3) # PYCHOK false! ta2, ta4, ta6 = fpowers(ta**2, 3) # PYCHOK false! tar = ta / r V4 = (a, tar / (2 * v), tar / (24 * v3) * fdot( (1, 3, -9), 5 + x2, ta2, ta2 * x2), tar / (720 * v5) * fdot( (61, 90, 45), 1, ta2, ta4)) csa = 1.0 / ca X5 = (_B0, csa / v, csa / (6 * v3) * fsum_(vr, ta, ta), csa / (120 * v5) * fdot( (5, 28, 24), 1, ta2, ta4), csa / (5040 * v7) * fdot( (61, 662, 1320, 720), ta, ta2, ta4, ta6)) d, d2, d3, d4, d5, d6, d7 = fpowers(e - _E0, 7) # PYCHOK false! a = fdot(V4, 1, -d2, d4, -d6) b = fdot(X5, 1, d, -d3, d5, -d7) self._latlon = _eLLb(degrees90(a), degrees180(b), datum=_OSGB36) return self._latlon3(LatLon, datum)
def toLatLon(self, LatLon=None): '''Convert this UTM coordinate to an (ellipsoidal) geodetic point. @keyword LatLon: Optional, ellipsoidal (sub-)class to use for the point (C{LatLon}) or C{None}. @return: This UTM coordinate as (I{LatLon}) or 5-tuple (lat, lon, datum, convergence, scale) if I{LatLon} is C{None}. @raise TypeError: If I{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 self._latlon: return self._latlon5(LatLon) E = self._datum.ellipsoid # XXX vs LatLon.datum.ellipsoid x = self._easting - _FalseEasting # relative to central meridian y = self._northing if self._hemi == 'S': # relative to equator y -= _FalseNorthing # from Karney 2011 Eq 15-22, 36 A0 = _K0 * E.A if A0 < EPS: raise UTMError('%s invalid: %r' % ('meridional', E.A)) x /= A0 # η eta y /= A0 # ξ ksi Ks = _Kseries(E.BetaKs, x, y) # Krüger series y = -Ks.ys(-y) # ξ' x = -Ks.xs(-x) # η' shx = sinh(x) cy, sy = cos(y), sin(y) H = hypot(shx, cy) if H < EPS: raise UTMError('%s invalid: %r' % ('H', H)) T = t0 = sy / H # τʹ q = 1.0 / E.e12 d = 1 sd = Fsum(T) # toggles on +/-1.12e-16 eg. 31 N 400000 5000000 while abs(d) > EPS: # 1e-12 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 = sd.fsum_(d) # τi a = atan(T) # lat b = atan2(shx, cy) + radians(_cmlon(self._zone)) ll = _LLEB(degrees90(a), degrees180(b), datum=self._datum, name=self.name) # convergence: Karney 2011 Eq 26, 27 p = -Ks.ps(-1) q = Ks.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 = ll return self._latlon5(LatLon)
def isenclosedby(latlon, points, wrap=False): # MCCABE 14 '''Determine whether a point is enclosed by a polygon defined by an array, list, sequence, set or tuple of points. @param latlon: The point (I{LatLon} or 2-tuple (lat, lon)). @param points: The points defining the polygon (I{LatLon}[]). @keyword wrap: Wrap lat-, wrap and unroll longitudes (bool). @return: True if I{latlon} is inside the polygon, False otherwise. @raise TypeError: Some I{points} are not I{LatLon}. @raise ValueError: Insufficient number of I{points} or invalid I{latlon}. @see: L{sphericalNvector.LatLon.isEnclosedBy}, L{sphericalTrigonometry.LatLon.isEnclosedBy} and U{MultiDop GeogContainPt<http://github.com/nasa/MultiDop>} (U{Shapiro et al. 2009, JTECH <http://journals.ametsoc.org/doi/abs/10.1175/2009JTECHA1256.1>} and U{Potvin et al. 2012, JTECH <http://journals.ametsoc.org/doi/abs/10.1175/JTECH-D-11-00019.1>}). ''' pts = LatLon2psxy(points, closed=True, radius=None, wrap=wrap) def _xy(i): x, y, _ = pts[i] if not wrap: x %= 360.0 if x < (x0 - 180): x += 360 elif x >= (x0 + 180): x -= 360 return x, y try: y0, x0 = latlon.lat, latlon.lon except AttributeError: try: y0, x0 = latlon[:2] except (IndexError, TypeError, ValueError): raise ValueError('%s invalid: %r' % ('latlon', latlon)) if wrap: x0, y0 = wrap180(x0), wrap90(y0) else: x0 %= 360.0 n = len(pts) e = m = False s = Fsum() x1, y1 = _xy(n - 1) for i in range(n): x2, y2 = _xy(i) dx, x2 = unroll180(x1, x2, wrap=wrap) # determine if polygon edge (x1, y1)..(x2, y2) straddles # point (lat, lon) or is on boundary, but do not count # edges on boundary as more than one crossing if abs(dx) < 180 and (x1 < x0 <= x2 or x2 < x0 <= x1): m = not m dy = (x0 - x1) * (y2 - y1) - (y0 - y1) * dx if (dy > 0 and dx >= 0) or (dy < 0 and dx <= 0): e = not e s.fadd(sin(radians(y2))) x1, y1 = x2, y2 # an odd number of meridian crossings means polygon contains # a pole, assume that is the hemisphere containing the polygon # mean and if polygon contains North Pole, flip the result if m and s.fsum() > 0: e = not e return e
def toLatLon(self, LatLon=None, eps=EPS, unfalse=True): '''Convert this UTM coordinate to an (ellipsoidal) geodetic point. @keyword LatLon: Optional, ellipsoidal (sub-)class to return the point (C{LatLon}) or C{None}. @keyword eps: Optional convergence limit, L{EPS} or above (C{float}). @keyword unfalse: Unfalse I{easting} and I{northing} if falsed (C{bool}). @return: This UTM coordinate as (I{LatLon}) or 5-tuple (lat, lon, datum, convergence, scale) if I{LatLon} is C{None}. @raise TypeError: If I{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_eps == eps: return self._latlon5(LatLon) E = self._datum.ellipsoid # XXX vs LatLon.datum.ellipsoid x = self._easting y = self._northing if unfalse and self._falsed: x -= _FalseEasting # relative to central meridian if self._hemisphere == 'S': # relative to equator y -= _FalseNorthing # from Karney 2011 Eq 15-22, 36 A0 = _K0 * E.A if A0 < EPS: raise UTMError('%s invalid: %r' % ('meridional', E.A)) x /= A0 # η eta y /= A0 # ξ ksi Ks = _Kseries(E.BetaKs, x, y) # Krüger series y = -Ks.ys(-y) # ξ' x = -Ks.xs(-x) # η' shx = sinh(x) sy, cy = sincos2(y) H = hypot(shx, cy) if H < EPS: raise UTMError('%s invalid: %r' % ('H', H)) d = 1.0 + eps q = 1.0 / E.e12 T = t0 = sy / H # τʹ sd = Fsum(T) while abs(d) > eps: 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 = sd.fsum2_(d) # τi, (τi - τi-1) a = atan(T) # lat b = atan2(shx, cy) + radians(_cmlon(self._zone)) ll = _LLEB(degrees90(a), degrees180(b), datum=self._datum, name=self.name) # convergence: Karney 2011 Eq 26, 27 p = -Ks.ps(-1) q = Ks.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, self._latlon_eps = ll, eps return self._latlon5(LatLon)
def toLatLon(self, LatLon=None, eps=EPS, unfalse=True): '''Convert this UTM coordinate to an (ellipsoidal) geodetic point. @keyword LatLon: Optional, ellipsoidal (sub-)class to return the point (C{LatLon}) or C{None}. @keyword eps: Optional convergence limit, L{EPS} or above (C{float}). @keyword unfalse: Unfalse B{C{easting}} and B{C{northing}} if falsed (C{bool}). @return: This UTM coordinate as (B{C{LatLon}}) or a L{LatLonDatum5Tuple}C{(lat, lon, datum, convergence, scale)} if B{C{LatLon}} is C{None}. @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('%s invalid: %r' % ('meridional', E.A)) 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('%s invalid: %r' % ('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)