def elevation(self): '''Get the elevation, tilt of this NED vector in degrees from horizontal, i.e. tangent to ellipsoid surface (C{degrees90}). ''' if self._elevation is None: self._elevation = neg(degrees90(asin(self.down / self.length))) return self._elevation
def _RF(x, y, z): # used by testElliptic.py '''Symmetric integral of the first kind C{_RF(x, y, z)}. @return: C{_RF(x, y, z)}. @see: U{C{_RF} definition<https://DLMF.NIST.gov/19.16.E1>} and U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}. ''' # Carlson, eqs 2.2 - 2.7 m = _1_0 A = fmean_(x, y, z) T = (A, x, y, z) Q = _Q(A, T, _TolRF) for _ in range(_TRIPS): if Q < abs(m * T[0]): # max 6 trips break _, _, T = _rsT(T) m *= _4_0 else: raise _convergenceError(_RF, x, y, z) m *= T[0] # An x = (A - x) / m y = (A - y) / m z = neg(x + y) e2 = x * y - z**2 e3 = x * y * z # Polynomial is <https://DLMF.NIST.gov/19.36.E1> # (1 - E2/10 + E3/14 + E2**2/24 - 3*E2*E3/44 # - 5*E2**3/208 + 3*E3**2/104 + E2**2*E3/16) # converted to Horner form ... H = Fsum( 6930 * e3, 15015 * e2**2, -16380 * e2, 17160) * e3 H += Fsum(10010 * e2, -5775 * e2**2, -24024) * e2 return H.fsum_(240240) / (240240 * sqrt(T[0]))
def _dms2deg(s, P, deg, min, sec): '''(INTERNAL) Helper for C{parseDDDMMSS} and C{parseDMS}. ''' deg += (min + (sec / _60_0)) / _60_0 if s == _MINUS_ or P in _SW_: deg = neg(deg) return deg
def angleTo(self, other, vSign=None, wrap=False): '''Compute the angle between this and an other vector. @arg other: The other vector (L{Vector3d}). @kwarg vSign: Optional vector, if supplied (and out of the plane of this and the other), angle is signed positive if this->other is clockwise looking along vSign or negative in opposite direction, otherwise angle is unsigned. @kwarg warp: Wrap/unroll the angle to +/-PI (c{bool}). @return: Angle (C{radians}). @raise TypeError: If B{C{other}} or B{C{vSign}} not a L{Vector3d}. ''' x = self.cross(other) s = x.length if s < EPS: return _0_0 # use vSign as reference to get sign of s if vSign and x.dot(vSign) < 0: s = neg(s) a = atan2(s, self.dot(other)) if wrap and abs(a) > PI: a -= copysign(PI2, a) return a
def forward4(self, lat, lon): '''Convert an (ellipsoidal) geodetic location to Cassini-Soldner easting and northing. @arg lat: Latitude of the location (C{degrees90}). @arg lon: Longitude of the location (C{degrees180}). @return: An L{EasNorAziRk4Tuple}C{(easting, northing, azimuth, reciprocal)}. @see: Method L{CassiniSoldner.forward}, L{CassiniSoldner.reverse} and L{CassiniSoldner.reverse4}. @raise CSSError: Invalid B{C{lat}} or B{C{lon}}. ''' g, M = self.datum.ellipsoid._geodesic_Math2 lat = Lat_(lat, Error=CSSError) d = M.AngDiff(self.lon0, Lon_(lon, Error=CSSError))[0] # _2sum r = g.Inverse(lat, -abs(d), lat, abs(d)) z1, a = r.azi1, (r.a12 * _0_5) z2, e = r.azi2, (r.s12 * _0_5) if e == 0: z = M.AngDiff(z1, z2)[0] * _0_5 # _2sum c = -90 if abs(d) > 90 else 90 z1, z2 = c - z, c + z if d < 0: a, e, z2 = neg(a), neg(e), z1 # z: azimuth of easting direction z = M.AngNormalize(z2) p = g.Line(lat, d, z, g.DISTANCE | g.GEODESICSCALE) # rk: reciprocal of azimuthal northing scale rk = p.ArcPosition(neg(a), g.GEODESICSCALE).M21 # rk = p._GenPosition(True, -a, g.DISTANCE)[7] # s, c = M.sincosd(p.EquatorialAzimuth()) s, c = M.sincosd(M.atan2d(p._salp0, p._calp0)) sb1 = copysign(c, lat) # -c if lat < 0 else c cb1 = copysign(s, 90 - abs(d)) # -abs(s) if abs(d) > 90 else abs(s) d = M.atan2d(sb1 * self._cb0 - cb1 * self._sb0, cb1 * self._cb0 + sb1 * self._sb0) n = self._meridian.ArcPosition(d, g.DISTANCE).s12 # n = self._meridian._GenPosition(True, d, g.DISTANCE)[4] r = EasNorAziRk4Tuple(e, n, z, rk) return self._xnamed(r)
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 _deltaX(self, sn, cn, dn, cX, fX): '''(INTERNAL) Helper for C{.deltaD} thru C{.deltaPi}. ''' if None in (cn, dn): t = self.classname + '.delta' + fX.__name__[1:] raise _invokationError(t, sn, cn, dn) if cn < 0: cn, sn = -cn, neg(sn) return fX(sn, cn, dn) * PI_2 / cX - atan2(sn, cn)
def _RJ(x, y, z, p): # used by testElliptic.py '''Symmetric integral of the third kind C{_RJ(x, y, z, p)}. @return: C{_RJ(x, y, z, p)}. @see: U{C{_RJ} definition<https://DLMF.NIST.gov/19.16.E2>} and U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}. ''' 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 = neg(_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 += _RC(1, 1 + e) / (m * d) m *= _4_0 m3 *= 64 else: raise _convergenceError(_RJ, x, y, z, p) m *= T[0] # An x = (A - x) / m y = (A - y) / m z = (A - z) / m xyz = x * y * z p = neg(x + y + z) * _0_5 p2 = p**2 e2 = fsum_(x * y, x * z, y * z, -3 * p2) return _horner(6 * 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 _Rad_(strRad, suffix, clip): try: r = float(strRad) except (TypeError, ValueError): strRad = strRad.strip() r = float( strRad.lstrip(_PLUSMINUS_).rstrip(suffix.upper()).strip()) if strRad[:1] == _MINUS_ or strRad[-1:] in _SW_: r = neg(r) return clipRadians(r, float(clip)) if clip else r
def toLatLon(self, LatLon=None, unfalse=True, **LatLon_kwds): '''Convert this UPS coordinate to an (ellipsoidal) geodetic point. @kwarg LatLon: Optional, ellipsoidal class to return the geodetic point (C{LatLon}) or C{None}. @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 C{B{LatLon}=None}. @return: This UPS 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 UPSError: Invalid meridional radius or H-value. ''' if self._latlon and self._latlon_args == unfalse: return self._latlon5(LatLon) E = self.datum.ellipsoid # XXX vs LatLon.datum.ellipsoid x, y = self.eastingnorthing2(falsed=not unfalse) r = hypot(x, y) t = (r / (_2_0 * self.scale0 * E.a / E.es_c)) if r > 0 else _EPS__2 t = E.es_tauf((1 / t - t) * _0_5) if self._pole == _N_: a, b, c = atan(t), atan2(x, -y), 1 else: a, b, c = neg(atan(t)), atan2(x, y), -1 a, b = degrees90(a), degrees180(b) if not self._band: self._band = _Band(a, b) if not self._hemisphere: self._hemisphere = _hemi(a) ll = _LLEB(a, b, datum=self._datum, name=self.name) ll._convergence = b * c # gamma ll._scale = _scale(E, r, t) if r > 0 else self.scale0 self._latlon_to(ll, unfalse) return self._latlon5(LatLon, **LatLon_kwds)
def _sigmaDwd(self, snu, cnu, dnu, snv, cnv, dnv): '''(INTERNAL) C{sigmaDwd}. @return: 2-Tuple C{(du, dv)}. @see: C{void TMExact::dwdsigma(real /*u*/, real snu, real cnu, real dnu, real /*v*/, real snv, real cnv, real dnv, real &du, real &dv)}. ''' snuv = snu * snv # Reciprocal of 55.9: dw / ds = dn(w)^2/_mv, # expanding complex dn(w) using A+S 16.21.4 d = self._mv * (cnv**2 + self._mu * snuv**2)**2 r = cnv * dnu * dnv i = cnu * snuv * self._mu du = (r**2 - i**2) / d dv = neg(2 * i * r / d) return du, dv
def _txif(self, ta): # called from .Ellipsoid.auxAuthalic '''(INTERNAL) Function M{tan-xi from tan-phi}. ''' E = self.datum.ellipsoid ca2 = _1_0 / (_1_0 + ta**2) sa = sqrt(ca2) * abs(ta) # enforce odd parity es1 = sa * E.e2 es2m1 = _1_0 - sa * es1 sp1 = _1_0 + sa es1p1 = sp1 / (_1_0 + es1) es1m1 = sp1 * (_1_0 - es1) es2m1a = es2m1 * E.e12 # e2m s = sqrt((ca2 / (es1p1 * es2m1a) + self._atanhee(ca2 / es1m1)) * (es1m1 / es2m1a + self._atanhee(es1p1))) t = (sa / es2m1 + self._atanhee(sa)) / s if ta < 0: t = neg(t) return t
def _zetaDwd(self, snu, cnu, dnu, snv, cnv, dnv): '''(INTERNAL) C{zetaDwd}. @return: 2-Tuple C{(du, dv)}. @see: C{void TMExact::dwdzeta(real /*u*/, real snu, real cnu, real dnu, real /*v*/, real snv, real cnv, real dnv, real &du, real &dv)}. ''' cnu2 = cnu**2 * self._mu cnv2 = cnv**2 dnuv = dnu * dnv dnuv2 = dnuv**2 snuv = snu * snv snuv2 = snuv**2 * self._mu # Lee 54.21 but write # (1 - dnu^2 * snv^2) = (cnv^2 + _mu * snu^2 * snv^2) # (see A+S 16.21.4) d = self._mv * (cnv2 + snuv2)**2 du = cnu * dnuv * (cnv2 - snuv2) / d dv = cnv * snuv * (cnu2 + dnuv2) / d return du, neg(dv)
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 C{B{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.eastingnorthing2(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 = neg(K.ys(-y)) # ξ' x = neg(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 = neg(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 __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 intersection(start1, end1, start2, end2, height=None, LatLon=LatLon, **LatLon_kwds): '''Locate the intersection of two paths each defined by two points or by a start point and an initial bearing. @arg start1: Start point of the first path (L{LatLon}). @arg end1: End point of the first path (L{LatLon}) or the initial bearing at the first start point (compass C{degrees360}). @arg start2: Start point of the second path (L{LatLon}). @arg end2: End point of the second path (L{LatLon}) or the initial bearing at the second start point (compass C{degrees360}). @kwarg height: Optional height at the intersection point, overriding the mean height (C{meter}). @kwarg LatLon: Optional class to return the intersection point (L{LatLon}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}=None}. @return: The intersection point (B{C{LatLon}}) or 3-tuple (C{degrees90}, C{degrees180}, height) if B{C{LatLon}} is C{None} or C{None} if no unique intersection exists. @raise TypeError: If B{C{start*}} or B{C{end*}} is not L{LatLon}. @raise ValueError: Intersection is ambiguous or infinite or the paths are parallel, coincident or null. @example: >>> p = LatLon(51.8853, 0.2545) >>> q = LatLon(49.0034, 2.5735) >>> i = intersection(p, 108.55, q, 32.44) # 50.9076°N, 004.5086°E ''' _Nvll.others(start1=start1) _Nvll.others(start2=start2) # If gc1 and gc2 are great circles through start and end points # (or defined by start point and bearing), then the candidate # intersections are simply gc1 × gc2 and gc2 × gc1. Most of the # work is deciding the correct intersection point to select! If # bearing is given, that determines the intersection, but if both # paths are defined by start/end points, take closer intersection. gc1, s1, e1 = _Nvll._gc3(start1, end1, 'end1') gc2, s2, e2 = _Nvll._gc3(start2, end2, 'end2') hs = start1.height, start2.height # there are two (antipodal) candidate intersection # points ... we have to choose the one to return i1 = gc1.cross(gc2, raiser=_paths_) # postpone computing i2 until needed # i2 = gc2.cross(gc1, raiser=_paths_) # selection of intersection point depends on how # paths are defined (by bearings or endpoints) if e1 and e2: # endpoint+endpoint d = sumOf((s1, s2, e1, e2)).dot(i1) hs += end1.height, end2.height elif e1 and not e2: # endpoint+bearing # gc2 x v2 . i1 +ve means v2 bearing points to i1 d = gc2.cross(s2).dot(i1) hs += end1.height, elif e2 and not e1: # bearing+endpoint # gc1 x v1 . i1 +ve means v1 bearing points to i1 d = gc1.cross(s1).dot(i1) hs += end2.height, else: # bearing+bearing # if gc x v . i1 is +ve, initial bearing is # towards i1, otherwise towards antipodal i2 d1 = gc1.cross(s1).dot(i1) # +ve means p1 bearing points to i1 d2 = gc2.cross(s2).dot(i1) # +ve means p2 bearing points to i1 if d1 > 0 and d2 > 0: d = 1 # both point to i1 elif d1 < 0 and d2 < 0: d = -1 # both point to i2 else: # d1, d2 opposite signs # intersection is at further-away intersection # point, take opposite intersection from mid- # point of v1 and v2 [is this always true?] d = neg(s1.plus(s2).dot(i1)) i = i1 if d > 0 else gc2.cross(gc1, raiser=_paths_) h = fmean(hs) if height is None else height kwds = _xkwds(LatLon_kwds, height=h, LatLon=LatLon) return i.toLatLon(**kwds) # Nvector(i.x, i.y, i.z).toLatLon(...)
def toUps8(latlon, lon=None, datum=None, Ups=Ups, pole=NN, falsed=True, strict=True, name=NN): '''Convert a lat-/longitude point to a UPS coordinate. @arg latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @kwarg lon: Optional longitude (C{degrees}) or C{None} if B{C{latlon}} is a C{LatLon}. @kwarg datum: Optional datum for this UPS coordinate, overriding B{C{latlon}}'s datum (C{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}). @kwarg Ups: Optional class to return the UPS coordinate (L{Ups}) or C{None}. @kwarg pole: Optional top/center of (stereographic) projection (C{str}, C{'N[orth]'} or C{'S[outh]'}). @kwarg falsed: False both easting and northing (C{bool}). @kwarg strict: Restrict B{C{lat}} to UPS ranges (C{bool}). @kwarg 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 or B{C{datum}} invalid. @raise ValueError: If B{C{lon}} value is missing or if B{C{latlon}} is invalid. @see: I{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 d = _ellipsoidal_datum(d, name=name) 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_0) < _TOL # at pole t = tan(radians(a)) T = E.es_taupf(t) r = hypot1(T) + abs(T) if T >= _0_0: r = _0_0 if A else _1_0 / 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 = neg(y) else: c = neg(c) if falsed: x += _Falsing y += _Falsing if Ups is None: r = UtmUps8Tuple(z, p, x, y, B, d, c, k, Error=UPSError) 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)