def _s1_qZ_C2(self, ca1, sa1, ta1, ca2, sa2, ta2): '''(INTERNAL) Compute C{sm1 / (s / qZ)} and C{C} for .__init__. ''' E = self.datum.ellipsoid b_a = E.b_a e2 = E.e2 tb1 = b_a * ta1 tb2 = b_a * ta2 dtb12 = b_a * (tb1 + tb2) scb12 = _1_0 + tb1**2 scb22 = _1_0 + tb2**2 esa1_2 = (_1_0 - e2 * sa1**2) \ * (_1_0 - e2 * sa2**2) esa12 = _1_0 + e2 * sa1 * sa2 dsn = _Dsn(ta2, ta1, sa2, sa1) axi, bxi, sxi = self._a_b_sxi3((ca1, sa1, ta1, scb12), (ca2, sa2, ta2, scb22)) dsxi = (esa12 / esa1_2 + self._Datanhee(sa2, sa1)) * dsn / (_2_0 * self._qx) C = fsum_(sxi * dtb12 / dsxi, scb22, scb12) / (_2_0 * scb22 * scb12) sa12 = fsum_(sa1, sa2, sa1 * sa2) axi *= (_1_0 + e2 * sa12) / (_1_0 + sa12) bxi *= e2 * fsum_(sa1, sa2, esa12) / esa1_2 + E.e12 * self._D2atanhee( sa1, sa2) s1_qZ = dsn * (axi * self._qZ - bxi) / (_2_0 * dtb12) return s1_qZ, C
def thomas_(phi2, phi1, lam21, datum=Datums.WGS84): '''Compute the I{angular} distance between two (ellipsoidal) points using U{Thomas'<https://apps.DTIC.mil/dtic/tr/fulltext/u2/703541.pdf>} formula. @arg phi2: End latitude (C{radians}). @arg phi1: Start latitude (C{radians}). @arg lam21: Longitudinal delta, M{end-start} (C{radians}). @kwarg datum: Ellipsoidal datum to use (L{Datum}). @return: Angular distance (C{radians}). @raise TypeError: Invalid B{C{datum}}. @see: Functions L{thomas}, L{cosineAndoyerLambert_}, L{cosineForsytheAndoyerLambert_}, L{cosineLaw_}, L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_}, L{flatPolar_}, L{haversine_} and L{vincentys_} and U{Geodesy-PHP <https://GitHub.com/jtejido/geodesy-php/blob/master/src/Geodesy/ Distance/ThomasFormula.php>}. ''' _xinstanceof(Datum, datum=datum) s2, c2, s1, c1, _, c21 = sincos2(phi2, phi1, lam21) E = datum.ellipsoid if E.f and abs(c1) > EPS and abs(c2) > EPS: r1 = atan(E.b_a * s1 / c1) r2 = atan(E.b_a * s2 / c2) j = (r2 + r1) / 2.0 k = (r2 - r1) / 2.0 sj, cj, sk, ck, sl_2, _ = sincos2(j, k, lam21 / 2.0) h = fsum_(sk**2, (ck * sl_2)**2, -(sj * sl_2)**2) if EPS < abs(h) < EPS1: u = 1 / (1 - h) d = 2 * atan(sqrt(h * u)) # == acos(1 - 2 * h) sd, cd = sincos2(d) if abs(sd) > EPS: u = 2 * (sj * ck)**2 * u v = 2 * (sk * cj)**2 / h x = u + v y = u - v t = d / sd s = 4 * t**2 e = 2 * cd a = s * e b = 2 * d c = t - (a - e) / 2.0 s = fsum_(a * x, c * x**2, -b * y, -e * y**2, s * x * y) * E.f / 16.0 s = fsum_(t * x, -y, -s) * E.f / 4.0 return d - s * sd # fall back to cosineLaw_ return acos(s1 * s2 + c1 * c2 * c21)
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)} using I{Rey-Jer You}'s transformation. @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} 1, 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}}. ''' x, y, z, name = _xyzn4(xyz, y, z, Error=EcefError) x2, y2, z2 = x**2, y**2, z**2 r2 = fsum_(x2, y2, z2) # = hypot3(x2, y2, z2)**2 E = self.ellipsoid e = sqrt(E.a2 - E.b2) e2 = e**2 u = sqrt(fsum_(r2, -e2, hypot(r2 - e2, 2 * e * z)) / 2) p = hypot(u, e) q = hypot(x, y) B = atan2(p * z, u * q) # beta0 = atan(p / u * z / q) sB, cB = sincos2(B) p *= E.a B += fsum_(u * E.b, -p, e2) * sB / (p / cB - e2 * cB) sB, cB = sincos2(B) h = hypot(z - E.b * sB, q - E.a * cB) if fsum_(x2, y2, z2 * E.a_b**2) < E.a2: h = -h # inside ellipsoid r = Ecef9Tuple( x, y, z, degrees(atan2(E.a * sB, E.b * cB)), # atan(E.a_b * tan(B)) degrees(atan2(y, x)), h, 1, # C=1 None, # M=None self.datum) return self._xnamed(r, name)
def _Hf(e0, e1, e2, e3, e4, e5): '''(INTERNAL) Horner form for C{_RD} and C{_RJ} below. ''' # Polynomial is <https://DLMF.NIST.gov/19.36.E2> # (1 - 3*E2/14 + E3/6 + 9*E2^2/88 - 3*E4/22 - 9*E2*E3/52 + 3*E5/26 # - E2^3/16 + 3*E3^2/40 + 3*E2*E4/20 + 45*E2^2*E3/272 # - 9*(E3*E4+E2*E5)/68) return fsum_( fsum_(471240, -540540 * e2) * e5, fsum_(612612 * e2, -540540 * e3, -556920) * e4, fsum_(306306 * e3, 675675 * e2**2, -706860 * e2, 680680) * e3, fsum_(417690 * e2, -255255 * e2**2, -875160) * e2, 4084080) / (4084080 * e1) + e0
def cosineForsytheAndoyerLambert_(phi2, phi1, lam21, datum=Datums.WGS84): '''Compute the I{angular} distance between two (ellipsoidal) points using the U{Forsythe-Andoyer-Lambert correction<https://www2.UNB.CA/gge/Pubs/TR77.pdf>} of the U{Law of Cosines<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>} formula. @arg phi2: End latitude (C{radians}). @arg phi1: Start latitude (C{radians}). @arg lam21: Longitudinal delta, M{end-start} (C{radians}). @kwarg datum: Ellipsoidal datum to use (L{Datum}). @return: Angular distance (C{radians}). @raise TypeError: Invalid B{C{datum}}. @see: Functions L{cosineForsytheAndoyerLambert}, L{cosineAndoyerLambert_}, L{cosineLaw_}, L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_}, L{flatPolar_}, L{haversine_}, L{thomas_} and L{vincentys_} and U{Geodesy-PHP <https://GitHub.com/jtejido/geodesy-php/blob/master/src/Geodesy/ Distance/ForsytheCorrection.php>}. ''' _xinstanceof(Datum, datum=datum) s2, c2, s1, c1, _, c21 = sincos2(phi2, phi1, lam21) r = acos(s1 * s2 + c1 * c2 * c21) E = datum.ellipsoid if E.f: sr, cr, s2r, _ = sincos2(r, r * 2) if abs(sr) > EPS: r2 = r**2 p = (s1 + s2)**2 / (1 + cr) q = (s1 - s2)**2 / (1 - cr) x = p + q y = p - q s = 8 * r2 / sr a = 64 * r + 2 * s * cr # 16 * r2 / tan(r) d = 48 * sr + s # 8 * r2 / tan(r) b = -2 * d e = 30 * s2r c = fsum_(30 * r, e / 2, s * cr) # 8 * r2 / tan(r) d = fsum_(a * x, b * y, -c * x**2, d * x * y, e * y**2) * E.f / 32.0 d = fsum_(d, -x * r, 3 * y * sr) * E.f / 4.0 r += d return r
def heightOf(angle, distance, radius=R_M): '''Determine the height above the (spherical) earth after traveling along a straight line at a given tilt. @param angle: Tilt angle above horizontal (C{degrees}). @param distance: Distance along the line (C{meter} or same units as B{C{radius}}). @keyword radius: Optional mean earth radius (C{meter}). @return: Height (C{meter}, same units as B{C{distance}} and B{C{radius}}). @raise ValueError: Invalid B{C{angle}}, B{C{distance}} or B{C{radius}}. @see: U{MultiDop geog_lib.GeogBeamHt<https://GitHub.com/NASA/MultiDop>} (U{Shapiro et al. 2009, JTECH <https://Journals.AMetSoc.org/doi/abs/10.1175/2009JTECHA1256.1>} and U{Potvin et al. 2012, JTECH <https://Journals.AMetSoc.org/doi/abs/10.1175/JTECH-D-11-00019.1>}). ''' d, r = distance, radius if d > r: d, r = r, d if d > EPS: d = d / float(r) s = sin(radians(angle)) s = fsum_(1, 2 * s * d, d**2) if s > 0: return r * sqrt(s) - float(radius) raise ValueError('%s%r' % ('heightOf', (angle, distance, radius)))
def _RD(x, y, z): # used by testElliptic.py '''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 += 1.0 / (m * s[2] * (t + r)) m *= 4 else: raise _convergenceError(_RD, x, y, z) m *= T[0] # An x = (x - A) / m y = (y - A) / m z = (x + y) / 3.0 z2 = z**2 xy = x * y return _Horner(3 * 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 _RF(x, y, z): # used by testElliptic.py '''Symmetric integral of the first kind C{_RF}. @return: C{_RF(x, y, z)}. @see: U{C{_RF} definition<https://DLMF.NIST.gov/19.16.E1>}. ''' # Carlson, eqs 2.2 - 2.7 m = 1.0 A = fsum_(x, y, z) / 3.0 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 else: raise _convergenceError(_RF, x, y, z) m *= T[0] # An x = (A - x) / m y = (A - y) / m z = -(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 heightOf(angle, distance, radius=R_M): '''Determine the height above the (spherical) earth after traveling along a straight line at a given tilt. @arg angle: Tilt angle above horizontal (C{degrees}). @arg distance: Distance along the line (C{meter} or same units as B{C{radius}}). @kwarg radius: Optional mean earth radius (C{meter}). @return: Height (C{meter}, same units as B{C{distance}} and B{C{radius}}). @raise ValueError: Invalid B{C{angle}}, B{C{distance}} or B{C{radius}}. @see: U{MultiDop geog_lib.GeogBeamHt<https://GitHub.com/NASA/MultiDop>} (U{Shapiro et al. 2009, JTECH <https://Journals.AMetSoc.org/doi/abs/10.1175/2009JTECHA1256.1>} and U{Potvin et al. 2012, JTECH <https://Journals.AMetSoc.org/doi/abs/10.1175/JTECH-D-11-00019.1>}). ''' r = h = Radius(radius) d = abs(Distance(distance)) if d > h: d, h = h, d if d > EPS: d = d / h # PyChecker chokes on ... /= ... s = sin(Phi_(angle, name='angle', clip=180)) s = fsum_(1, 2 * s * d, d**2) if s > 0: return h * sqrt(s) - r raise InvalidError(angle=angle, distance=distance, radius=radius)
def flatPolar_(phi2, phi1, lam21): '''Compute the I{angular} distance between two (spherical) points using the U{polar coordinate flat-Earth<https://WikiPedia.org/wiki/ Geographical_distance#Polar_coordinate_flat-Earth_formula>} formula. @arg phi2: End latitude (C{radians}). @arg phi1: Start latitude (C{radians}). @arg lam21: Longitudinal delta, M{end-start} (C{radians}). @return: Angular distance (C{radians}). @see: Functions L{flatPolar}, L{cosineAndoyerLambert_}, L{cosineForsytheAndoyerLambert_}, L{cosineLaw_}, L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_}, L{haversine_}, L{thomas_} and L{vincentys_}. ''' a1 = abs(PI_2 - phi1) # co-latitude a2 = abs(PI_2 - phi2) # co-latitude ab = abs(2 * a1 * a2 * cos(lam21)) a = max(a1, a2, ab) if a > EPS: s = fsum_((a1 / a)**2, (a2 / a)**2, -ab / a**2) a *= sqrt(s) if s > 0 else 0 return a
def _intersects2(center1, r1, center2, r2, sphere=True, too_d=None, # in .ellipsoidalBase._intersections2 Vector=None, **Vector_kwds): # (INTERNAL) Intersect two spheres or circles, see L{intersections2} # above, separated to allow callers to embellish any exceptions def _V3(x, y, z): v = Vector3d(x, y, z) n = intersections2.__name__ return _V_n(v, n, Vector, Vector_kwds) def _xV3(c1, u, x, y): xy1 = x, y, _1_0 # transform to original space return _V3(fdot(xy1, u.x, -u.y, c1.x), fdot(xy1, u.y, u.x, c1.y), _0_0) c1 = _otherV3d(sphere=sphere, center1=center1) c2 = _otherV3d(sphere=sphere, center2=center2) if r1 < r2: # r1, r2 == R, r c1, c2 = c2, c1 r1, r2 = r2, r1 m = c2.minus(c1) d = m.length if d < max(r2 - r1, EPS): raise ValueError(_near_concentric_) o = fsum_(-d, r1, r2) # overlap == -(d - (r1 + r2)) # compute intersections with c1 at (0, 0) and c2 at (d, 0), like # <https://MathWorld.Wolfram.com/Circle-CircleIntersection.html> if o > EPS: # overlapping, r1, r2 == R, r x = _radical2(d, r1, r2).xline y = _1_0 - (x / r1)**2 if y > EPS: y = r1 * sqrt(y) # y == a / 2 elif y < 0: raise ValueError(_invalid_) else: # abutting y = _0_0 elif o < 0: t = d if too_d is None else too_d raise ValueError(_too_(Fmt.distant(t))) else: # abutting x, y = r1, _0_0 u = m.unit() if sphere: # sphere center and radius c = c1 if x < EPS else ( c2 if x > EPS1 else c1.plus(u.times(x))) t = _V3(c.x, c.y, c.z), Radius(y) elif y > 0: # intersecting circles t = _xV3(c1, u, x, y), _xV3(c1, u, x, -y) else: # abutting circles t = _xV3(c1, u, x, 0) t = t, t return t
def _RF(x, y, z=None): '''Symmetric integral of the first kind C{_RF}. @return: C{_RF(x, y, z)}. @see: U{C{_RF} definition<https://DLMF.NIST.gov/19.16.E1>}. ''' if z is None: # Carlson, eqs 2.36 - 2.38 a, b = sqrt(x), sqrt(y) if a < b: a, b = b, a while abs(a - b) > (_tolRG0 * a): # max 4 trips b, a = sqrt(a * b), (a + b) * 0.5 return PI / (a + b) # Carlson, eqs 2.2 - 2.7 m = 1.0 A = fsum_(x, y, z) / 3.0 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 else: raise EllipticError('no %s convergence' % ('RF', )) m *= T[0] # An x = (A - x) / m y = (A - y) / m z = -(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) # convert to Horner form ... return fsum_( fsum_(6930 * e3, 15015 * e2**2, -16380 * e2, 17160) * e3, fsum_(10010 * e2, -5775 * e2**2, -24024) * e2, 240240) / (240240 * sqrt(T[0]))
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 _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 _fE(sn, cn, dn): sn2, cn2, dn2 = sn**2, cn**2, dn**2 kp2, k2 = self.kp2, self.k2 if k2 <= 0: # Carlson, eq. 4.6, <https://DLMF.NIST.gov/19.25.E9> ei = _RF(cn2, dn2, 1) - k2 * sn2 * _RD_3(cn2, dn2, 1) elif kp2 >= 0: # <https://DLMF.NIST.gov/19.25.E10> ei = fsum_(kp2 * _RF(cn2, dn2, 1), kp2 * k2 * sn2 * _RD_3(cn2, 1, dn2), k2 * abs(cn) / dn) else: # <https://DLMF.NIST.gov/19.25.E11> ei = dn / abs(cn) - kp2 * sn2 * _RD_3(dn2, 1, cn2) return ei * abs(sn)
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 _RG(x, y, z): # used by testElliptic.py '''Symmetric integral of the second kind C{_RG(x, y, z)}. @return: C{_RG(x, y, z)}. @see: U{C{_RG} definition<https://DLMF.NIST.gov/19.16.E3>} and U{Carlson<https://ArXiv.org/pdf/math/9409227.pdf>}. ''' 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 _RG(x, y, z): # used by testElliptic.py '''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 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 _intersects2(c1, rad1, c2, rad2, radius=R_M, # in .ellipsoidalBase._intersects2 height=None, wrap=True, too_d=None, LatLon=LatLon, **LatLon_kwds): # (INTERNAL) Intersect two spherical circles, see L{intersections2} # above, separated to allow callers to embellish any exceptions def _dest3(bearing, h): a, b = _destination2(a1, b1, r1, bearing) return _latlon3(degrees90(a), degrees180(b), h, intersections2, LatLon, **LatLon_kwds) r1, r2, f = _rads3(rad1, rad2, radius) if f: # swapped c1, c2 = c2, c1 # PYCHOK swap a1, b1 = c1.philam a2, b2 = c2.philam db, b2 = unrollPI(b1, b2, wrap=wrap) d = vincentys_(a2, a1, db) # radians if d < max(r1 - r2, EPS): raise ValueError(_near_concentric_) x = fsum_(r1, r2, -d) # overlap if x > EPS: sd, cd, sr1, cr1, _, cr2 = sincos2(d, r1, r2) x = sd * sr1 if abs(x) < EPS: raise ValueError(_invalid_) x = acos1((cr2 - cd * cr1) / x) # 0 <= x <= PI elif x < 0: t = (d * radius) if too_d is None else too_d raise ValueError(_too_distant_fmt_ % (t,)) if height is None: # "radical height" f = _radical2(d, r1, r2).ratio h = Height(favg(c1.height, c2.height, f=f)) else: h = Height(height) b = bearing_(a1, b1, a2, b2, final=False, wrap=wrap) if x < _EPS_I2: # externally ... r = _dest3(b, h) elif x > _PI_EPS_I2: # internally ... r = _dest3(b + PI, h) else: return _dest3(b + x, h), _dest3(b - x, h) return r, r # ... abutting circles
def intersect(self, p1, p2, edge): # compute intersection # of polygon edge p1 to p2 and the current clip edge, # where p1 and p2 are known to NOT be located on the # same side of or on the current clip edge # <https://StackOverflow.com/questions/563198/ # how-do-you-detect-where-two-line-segments-intersect> fy = float(p2.lat - p1.lat) fx = float(p2.lon - p1.lon) fp = fy * self._dx - fx * self._dy if abs(fp) < EPS: raise AssertionError('clipSH.intersect') h = fsum_(self._xy, -p1.lat * self._dx, p1.lon * self._dy) / fp y = p1.lat + h * fy x = p1.lon + h * fx return _LLi_(y, x, p1.classof, edge)
def toNvector(self, Nvector=None, datum=None, **kwds): # PYCHOK Datums.WGS84 '''Convert this cartesian to C{n-vector} components. @keyword Nvector: Optional (sub-)class to return the C{n-vector} components (C{Nvector}) or C{None}. @keyword datum: Optional datum (L{Datum}) overriding this cartesian's datum. @keyword kwds: Optional, additional B{C{Nvector}} keyword arguments, ignored if C{B{Nvector}=None}. @return: Unit vector B{C{Nvector}} or a L{Vector4Tuple}C{(x, y, z, h)} if B{C{Nvector}=None}. @raise ValueError: The B{C{Cartesian}} at origin. ''' d = datum or self.datum r = self._v4t if r is None or self.datum != d: E = d.ellipsoid x, y, z = self.to3xyz() # Kenneth Gade eqn 23 p = hypot2(x, y) * E.a2_ q = (z**2 * E.e12) * E.a2_ r = fsum_(p, q, -E.e4) / 6 s = (p * q * E.e4) / (4 * r**3) t = cbrt(fsum_(1, s, sqrt(s * (2 + s)))) u = r * fsum_(1, t, 1 / t) v = sqrt(u**2 + E.e4 * q) w = E.e2 * fsum_(u, v, -q) / (2 * v) k = sqrt(fsum_(u, v, w**2)) - w if abs(k) < EPS: raise ValueError('%s: %r' % ('origin', self)) e = k / (k + E.e2) t = hypot(e * hypot(x, y), z) if t < EPS: raise ValueError('%s: %r' % ('origin', self)) h = fsum_(k, E.e2, -1) / k * t s = e / t r = Vector4Tuple(x * s, y * s, z / t, h) self._v4t = r if d == self.datum else None if Nvector is not None: r = Nvector(r.x, r.y, r.z, h=r.h, datum=d, **kwds) return self._xnamed(r)
def rhumbMidpointTo(self, other, height=None): '''Return the (loxodromic) midpoint between this and an other point. @arg other: The other point (spherical LatLon). @kwarg height: Optional height, overriding the mean height (C{meter}). @return: The midpoint (spherical C{LatLon}). @raise TypeError: The I{other} point is not spherical. @raise ValueError: Invalid B{C{height}}. @example: >>> p = LatLon(51.127, 1.338) >>> q = LatLon(50.964, 1.853) >>> m = p.rhumb_midpointTo(q) >>> m.toStr() # '51.0455°N, 001.5957°E' ''' self.others(other) # see <https://MathForum.org/library/drmath/view/51822.html> a1, b1 = self.philam a2, b2 = other.philam if abs(b2 - b1) > PI: b1 += PI2 # crossing anti-meridian a3 = favg(a1, a2) b3 = favg(b1, b2) f1 = tanPI_2_2(a1) if abs(f1) > EPS: f2 = tanPI_2_2(a2) f = f2 / f1 if abs(f) > EPS: f = log(f) if abs(f) > EPS: f3 = tanPI_2_2(a3) b3 = fsum_(b1 * log(f2), -b2 * log(f1), (b2 - b1) * log(f3)) / f h = self._havg(other) if height is None else Height(height) return self.classof(degrees90(a3), degrees180(b3), height=h)
def toNvector(self, datum=Datums.WGS84): '''Convert this cartesian to an (ellipsoidal) n-vector. @keyword datum: Optional datum to use (L{Datum}). @return: The ellipsoidal n-vector (L{Nvector}). @raise ValueError: The B{C{Cartesian}} at origin. @example: >>> from ellipsoidalNvector import LatLon >>> c = Cartesian(3980581, 97, 4966825) >>> n = c.toNvector() # (0.62282, 0.000002, 0.78237, +0.24) ''' if self._Nv is None or datum != self._Nv.datum: E = datum.ellipsoid x, y, z = self.to3xyz() # Kenneth Gade eqn 23 p = (x**2 + y**2) * E.a2_ q = (z**2 * E.e12) * E.a2_ r = fsum_(p, q, -E.e4) / 6 s = (p * q * E.e4) / (4 * r**3) t = cbrt(fsum_(1, s, sqrt(s * (2 + s)))) u = r * fsum_(1, t, 1 / t) v = sqrt(u**2 + E.e4 * q) w = E.e2 * fsum_(u, v, -q) / (2 * v) k = sqrt(fsum_(u, v, w**2)) - w if abs(k) < EPS: raise ValueError('%s: %r' % ('origin', self)) e = k / (k + E.e2) d = e * hypot(x, y) t = hypot(d, z) if t < EPS: raise ValueError('%s: %r' % ('origin', self)) h = fsum_(k, E.e2, -1) / k * t s = e / t self._Nv = Nvector(x * s, y * s, z / t, h=h, datum=datum, name=self.name) return self._Nv
def horizon(height, radius=R_M, refraction=False): '''Determine the distance to the horizon from a given altitude above the (spherical) earth. @param height: Altitude (C{meter} or same units as B{C{radius}}). @keyword radius: Optional mean earth radius (C{meter}). @keyword refraction: Consider atmospheric refraction (C{bool}). @return: Distance (C{meter}, same units as B{C{height}} and B{C{radius}}). @raise ValueError: Invalid B{C{height}} or B{C{radius}}. @see: U{Distance to horizon<https://www.EdWilliams.org/avform.htm#Horizon>}. ''' if min(height, radius) < 0: raise ValueError('%s%r' % ('horizon', (height, radius))) if refraction: d2 = 2.415750694528 * height * radius # 2.0 / 0.8279 else: d2 = height * fsum_(radius, radius, height) return sqrt(d2)
def horizon(height, radius=R_M, refraction=False): '''Determine the distance to the horizon from a given altitude above the (spherical) earth. @arg height: Altitude (C{meter} or same units as B{C{radius}}). @kwarg radius: Optional mean earth radius (C{meter}). @kwarg refraction: Consider atmospheric refraction (C{bool}). @return: Distance (C{meter}, same units as B{C{height}} and B{C{radius}}). @raise ValueError: Invalid B{C{height}} or B{C{radius}}. @see: U{Distance to horizon<https://www.EdWilliams.org/avform.htm#Horizon>}. ''' h, r = Height(height), Radius(radius) if min(h, r) < 0: raise InvalidError(height=height, radius=radius) if refraction: d2 = 2.415750694528 * h * r # 2.0 / 0.8279 else: d2 = h * fsum_(r, r, h) return sqrt(d2)
def _scaled(self, tau, d2, snu, cnu, dnu, snv, cnv, dnv): '''(INTERNAL) C{scaled}. @note: Argument B{C{d2}} is C{_mu * cnu**2 + _mv * cnv**2} from C{._sigma3} or C{._zeta3}. @return: 2-Tuple C{(convergence, scale)}. @see: C{void TMExact::Scale(real tau, real /*lam*/, real snu, real cnu, real dnu, real snv, real cnv, real dnv, real &gamma, real &k)}. ''' mu, mv = self._mu, self._mv cnudnv = cnu * dnv # Lee 55.12 -- negated for our sign convention. g gives # the bearing (clockwise from true north) of grid north g = atan2(mv * cnv * snv * snu, cnudnv * dnu) # Lee 55.13 with nu given by Lee 9.1 -- in sqrt change # the numerator from # # (1 - snu^2 * dnv^2) to (_mv * snv^2 + cnu^2 * dnv^2) # # to maintain accuracy near phi = 90 and change the # denomintor from # (dnu^2 + dnv^2 - 1) to (_mu * cnu^2 + _mv * cnv^2) # # to maintain accuracy near phi = 0, lam = 90 * (1 - e). # Similarly rewrite sqrt term in 9.1 as # # _mv + _mu * c^2 instead of 1 - _mu * sin(phi)^2 q2 = (mv * snv**2 + cnudnv**2) / d2 # originally: sec2 = 1 + tau**2 # sec(phi)^2 # k = sqrt(mv + mu / sec2) * sqrt(sec2) * sqrt(q2) # = sqrt(mv + mv * tau**2 + mu) * sqrt(q2) k = sqrt(fsum_(mu, mv, mv * tau**2)) * sqrt(q2) return degrees(g), k * self._k0
def toOsgr(latlon, lon=None, datum=Datums.WGS84, Osgr=Osgr, name=NN, **Osgr_kwds): '''Convert a lat-/longitude point to an OSGR coordinate. @arg latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @kwarg lon: Optional longitude in degrees (scalar or C{None}). @kwarg datum: Optional datum to convert B{C{lat, lon}} from (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}). @kwarg Osgr: Optional class to return the OSGR coordinate (L{Osgr}) or C{None}. @kwarg name: Optional B{C{Osgr}} name (C{str}). @kwarg Osgr_kwds: Optional, additional B{C{Osgr}} keyword arguments, ignored if B{C{Osgr=None}}. @return: The OSGR coordinate (B{C{Osgr}}) or an L{EasNor2Tuple}C{(easting, northing)} if B{C{Osgr}} is C{None}. @raise OSGRError: Invalid B{C{latlon}} or B{C{lon}}. @raise TypeError: Non-ellipsoidal B{C{latlon}} or invalid B{C{datum}} or conversion failed. @example: >>> p = LatLon(52.65798, 1.71605) >>> r = toOsgr(p) # TG 51409 13177 >>> # for conversion of (historical) OSGB36 lat-/longitude: >>> r = toOsgr(52.65757, 1.71791, datum=Datums.OSGB36) ''' if not isinstance(latlon, _LLEB): # XXX fix failing _LLEB.convertDatum() latlon = _LLEB(*parseDMS2(latlon, lon), datum=datum) elif lon is not None: raise OSGRError(lon=lon, txt='not %s' % (None, )) elif not name: # use latlon.name name = nameof(latlon) # if necessary, convert to OSGB36 first ll = _ll2datum(latlon, _Datums_OSGB36, _latlon_) try: a, b = ll.philam except AttributeError: a, b = map1(radians, ll.lat, ll.lon) sa, ca = sincos2(a) E = _Datums_OSGB36.ellipsoid s = E.e2s2(sa) # r, v = E.roc2_(sa, _F0); r = v / r v = E.a * _F0 / sqrt(s) # nu r = s / E.e12 # nu / rho == v / (v * E.e12 / s) == s / E.e12 x2 = r - 1 # η2 ta = tan(a) ca3, ca5 = fpowers(ca, 5, 3) # PYCHOK false! ta2, ta4 = fpowers(ta, 4, 2) # PYCHOK false! vsa = v * sa I4 = (E.b * _F0 * _M(E.Mabcd, a) + _N0, (vsa / 2) * ca, (vsa / 24) * ca3 * fsum_(5, -ta2, 9 * x2), (vsa / 720) * ca5 * fsum_(61, ta4, -58 * ta2)) V4 = (_E0, (v * ca), (v / 6) * ca3 * (r - ta2), (v / 120) * ca5 * fdot( (-18, 1, 14, -58), ta2, 5 + ta4, x2, ta2 * x2)) d, d2, d3, d4, d5, d6 = fpowers(b - _B0, 6) # PYCHOK false! n = fdot(I4, 1, d2, d4, d6) e = fdot(V4, 1, d, d3, d5) if Osgr is None: r = _EasNor2Tuple(e, n) else: r = Osgr(e, n, datum=_Datums_OSGB36, **Osgr_kwds) if lon is None and isinstance(latlon, _LLEB): r._latlon = latlon # XXX weakref(latlon)? return _xnamed(r, name)
def toLatLon(self, LatLon=None, datum=Datums.WGS84): '''Convert this OSGR coordinate to an (ellipsoidal) geodetic point. While OS grid references are based on the OSGB36 datum, the I{Ordnance Survey} have deprecated the use of OSGB36 for lat-/longitude coordinates (in favour of WGS84). Hence, this method returns WGS84 by default with OSGB36 as an option, U{see<https://www.OrdnanceSurvey.co.UK/blog/2014/12/2>}. 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.} @kwarg LatLon: Optional ellipsoidal class to return the geodetic point (C{LatLon}) or C{None}. @kwarg datum: Optional datum to convert to (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}, L{Ellipsoid2} or L{a_f2Tuple}). @return: The geodetic point (B{C{LatLon}}) or a L{LatLonDatum3Tuple}C{(lat, lon, datum)} if B{C{LatLon}} is C{None}. @raise OSGRError: No convergence. @raise TypeError: If B{C{LatLon}} is not ellipsoidal or B{C{datum}} is invalid or 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 = self.datum.ellipsoid # _Datums_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, n_N0 sa = Fsum(a) for self._iteration in range(1, _TRIPS): a = sa.fsum_(m / a_F0) m = n_N0 - b_F0 * _M(E.Mabcd, a) # meridional arc if abs(m) < _10um: break else: t = _dot_(_item_ps(self.classname, self.toStr(prec=-3)), self.toLatLon.__name__) raise OSGRError(_no_convergence_, txt=t) sa, ca = sincos2(a) s = E.e2s2(sa) # r, v = E.roc2_(sa, _F0) v = a_F0 / sqrt(s) # nu r = v * E.e12 / s # rho = a_F0 * E.e12 / pow(s, 1.5) == a_F0 * E.e12 / (s * sqrt(s)) vr = v / r # == s / E.e12 x2 = vr - 1 # η2 ta = tan(a) 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, ta2, ta2), csa / (120 * v5) * fdot( (5, 28, 24), 1, ta2, ta4), csa / (5040 * v7) * fdot( (61, 662, 1320, 720), 1, 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) r = _LLEB(degrees90(a), degrees180(b), datum=self.datum, name=self.name) r._iteration = self._iteration # only ellipsoidal LatLon self._latlon = r return self._latlon3(LatLon, datum)
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 C{B{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 = Meter(x=x) y = Meter(y=y) 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_0 * y_ * nrho0, y_ * ny) * k0 / den # dsxia = scxi0 * dsxi dsxia = -self._scxi0 * (_2_0 * nrho0 + n0 * drho) * drho / self._qZa2 t = _1_0 - dsxia * (_2_0 * txi0 + dsxia) txi = (txi0 + dsxia) / (sqrt(t) if t > _EPS__4 else _EPS__2) ta = self._tanf(txi) lat = atand(ta * self._sign) th = atan2(nx, y1) lon = degrees(th / self._k02n0 if n0 else x / (y1 * k0)) if lon0: lon += _norm180(lon0) lon = _norm180(lon) if LatLon is None: g = degrees360(self._sign * th) if den: k0 = self._azik(nrho0 + n0 * drho, ta) r = Albers7Tuple(x, y, lat, lon, g, k0, self.datum) else: kwds = _xkwds(LatLon_kwds, datum=self.datum) r = LatLon(lat, lon, **kwds) return self._xnamed(r, name=name)