def intersection(start1, end1, start2, end2, height=None, wrap=False, LatLon=LatLon): '''Compute the intersection point of two paths both defined by two points or a start point and bearing from North. @param start1: Start point of the first path (L{LatLon}). @param end1: End point ofthe first path (L{LatLon}) or the initial bearing at the first start point (compass C{degrees360}). @param start2: Start point of the second path (L{LatLon}). @param end2: End point of the second path (L{LatLon}) or the initial bearing at the second start point (compass C{degrees360}). @keyword height: Optional height for the intersection point, overriding the mean height (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @keyword LatLon: Optional (sub-)class to return the intersection point (L{LatLon}) or C{None}. @return: The intersection point (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. An alternate intersection point might be the L{antipode} to the returned result. @raise TypeError: A B{C{start}} or B{C{end}} point 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) >>> s = LatLon(49.0034, 2.5735) >>> i = intersection(p, 108.547, s, 32.435) # '50.9078°N, 004.5084°E' ''' _Trll.others(start1, name='start1') _Trll.others(start2, name='start2') hs = [start1.height, start2.height] a1, b1 = start1.to2ab() a2, b2 = start2.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) r12 = haversine_(a2, a1, db) if abs(r12) < EPS: # [nearly] coincident points a, b = map1(degrees, favg(a1, a2), favg(b1, b2)) # see <https://www.EdWilliams.org/avform.htm#Intersection> elif isscalar(end1) and isscalar(end2): # both bearings sa1, ca1, sa2, ca2, sr12, cr12 = sincos2(a1, a2, r12) x1, x2 = (sr12 * ca1), (sr12 * ca2) if abs(x1) < EPS or abs(x2) < EPS: raise ValueError('intersection %s: %r vs %r' % ('parallel', (start1, end1), (start2, end2))) # handle domain error for equivalent longitudes, # see also functions asin_safe and acos_safe at # <https://www.EdWilliams.org/avform.htm#Math> t1, t2 = map1(acos1, (sa2 - sa1 * cr12) / x1, (sa1 - sa2 * cr12) / x2) if sin(db) > 0: t12, t21 = t1, PI2 - t2 else: t12, t21 = PI2 - t1, t2 t13, t23 = map1(radiansPI2, end1, end2) x1, x2 = map1( wrapPI, t13 - t12, # angle 2-1-3 t21 - t23) # angle 1-2-3 sx1, cx1, sx2, cx2 = sincos2(x1, x2) if sx1 == 0 and sx2 == 0: # max(abs(sx1), abs(sx2)) < EPS raise ValueError('intersection %s: %r vs %r' % ('infinite', (start1, end1), (start2, end2))) sx3 = sx1 * sx2 # if sx3 < 0: # raise ValueError('intersection %s: %r vs %r' % ('ambiguous', # (start1, end1), (start2, end2))) x3 = acos1(cr12 * sx3 - cx2 * cx1) r13 = atan2(sr12 * sx3, cx2 + cx1 * cos(x3)) a, b = _destination2(a1, b1, r13, t13) # choose antipode for opposing bearings if _xb(a1, b1, end1, a, b, wrap) < 0 or \ _xb(a2, b2, end2, a, b, wrap) < 0: a, b = antipode(a, b) else: # end point(s) or bearing(s) x1, d1 = _x3d2(start1, end1, wrap, '1', hs) x2, d2 = _x3d2(start2, end2, wrap, '2', hs) x = x1.cross(x2) if x.length < EPS: # [nearly] colinear or parallel paths raise ValueError('intersection %s: %r vs %r' % ('colinear', (start1, end1), (start2, end2))) a, b = x.to2ll() # choose intersection similar to sphericalNvector d1 = _xdot(d1, a1, b1, a, b, wrap) d2 = _xdot(d2, a2, b2, a, b, wrap) if (d1 < 0 and d2 > 0) or (d1 > 0 and d2 < 0): a, b = antipode(a, b) h = fmean(hs) if height is None else height r = LatLon3Tuple(a, b, h) if LatLon is None else \ LatLon(a, b, height=h) return _xnamed(r, intersection.__name__)
def _mdef(self, lat): '''(INTERNAL) Compute m(lat). ''' s, c = sincos2(lat) s *= self._e return c / sqrt(1 - s**2)
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. @param xyz: Either an L{Ecef9Tuple}, an C{(x, y, z)} 3-tuple or C{scalar} ECEF C{x} coordinate in C{meter}. @keyword y: ECEF C{y} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{z}}. @keyword z: ECEF C{z} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{y}}. @keyword 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 _inverse(self, other, azis, wrap): '''(INTERNAL) Inverse Vincenty method. @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise ValueError: If this and the B{C{other}} point's L{Datum} ellipsoids are not compatible. @raise VincentyError: Vincenty fails to converge for the current L{LatLon.epsilon} and L{LatLon.iterations} limit and/or if this and the B{C{other}} point are coincident or near-antipodal. ''' E = self.ellipsoids(other) c1, s1, _ = _r3(self.lat, E.f) c2, s2, _ = _r3(other.lat, E.f) c1c2, s1c2 = c1 * c2, s1 * c2 c1s2, s1s2 = c1 * s2, s1 * s2 dl, _ = unroll180(self.lon, other.lon, wrap=wrap) ll = dl = radians(dl) for _ in range(self._iterations): ll_ = ll sll, cll = sincos2(ll) ss = hypot(c2 * sll, c1s2 - s1c2 * cll) if ss < EPS: # coincident or antipodal, ... if self.isantipodeTo(other, eps=self._epsilon): raise VincentyError('%s, %r %sto %r' % ('ambiguous', self, 'antipodal ', other)) d = 0.0 # like Karney, ... if azis: # return zeros d = d, 0, 0 return d cs = s1s2 + c1c2 * cll s = atan2(ss, cs) sa = c1c2 * sll / ss c2a = 1 - sa**2 if abs(c2a) < EPS: c2a = 0 # equatorial line ll = dl + E.f * sa * s else: c2sm = cs - 2 * s1s2 / c2a ll = dl + _dl(E.f, c2a, sa, s, cs, ss, c2sm) if abs(ll - ll_) < self._epsilon: break # # omitted and applied only after failure to converge below, see footnote # # under Inverse at <https://WikiPedia.org/wiki/Vincenty's_formulae> # # <https://GitHub.com/ChrisVeness/geodesy/blob/master/latlon-vincenty.js> # elif abs(ll) > PI and self.isantipodeTo(other, eps=self._epsilon): # raise VincentyError('%s, %r %sto %r' % ('ambiguous', self, # 'antipodal ', other)) else: t = 'antipodal ' if self.isantipodeTo(other, eps=self._epsilon) else '' raise VincentyError('%s, %r %sto %r' % ('no convergence', self, t, other)) if c2a: # e22 == (a / b)**2 - 1 A, B = _p2(c2a * E.e22) s = A * (s - _ds(B, cs, ss, c2sm)) b = E.b # if self.height or other.height: # b += self._havg(other) d = b * s if azis: # forward and reverse azimuth sll, cll = sincos2(ll) f = degrees360(atan2(c2 * sll, c1s2 - s1c2 * cll)) r = degrees360(atan2(c1 * sll, -s1c2 + c1s2 * cll)) d = d, f, r return d
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 _zetaInv0(self, psi, lam): '''(INTERNAL) Starting point for C{zetaInv}. @return: 3-Tuple C{(u, v, trip)}. @see: C{bool TMExact::zetainv0(real psi, real lam, # radians real &u, real &v)}. ''' trip = False if psi < -self._e_PI_4 and lam > self._1_e2_PI_2 \ and psi < (lam - self._1_e_PI_2): # N.B. this branch is normally not taken because psi < 0 # is converted psi > 0 by Forward. # # There's a log singularity at w = w0 = Eu.K() + i * Ev.K(), # corresponding to the south pole, where we have, approximately # # psi = _e + i * pi/2 - _e * atanh(cos(i * (w - w0)/(1 + _mu/2))) # # Inverting this gives: h = sinh(1 - psi / self._e) a = (PI_2 - lam) / self._e s, c = sincos2(a) u = self._Eu.cK - asinh(s / hypot(c, h)) * self._mu_2_1 v = self._Ev.cK - atan2(c, h) * self._mu_2_1 elif psi < self._e_PI_2 and lam > self._1_e2_PI_2: # At w = w0 = i * Ev.K(), we have # # zeta = zeta0 = i * (1 - _e) * pi/2 # zeta' = zeta'' = 0 # # including the next term in the Taylor series gives: # # zeta = zeta0 - (_mv * _e) / 3 * (w - w0)^3 # # When inverting this, we map arg(w - w0) = [-90, 0] to # arg(zeta - zeta0) = [-90, 180] d = lam - self._1_e_PI_2 r = hypot(psi, d) # Error using this guess is about 0.21 * (rad/e)^(5/3) trip = r < self._e_TAYTOL # atan2(dlam-psi, psi+dlam) + 45d gives arg(zeta - zeta0) # in range [-135, 225). Subtracting 180 (since multiplier # is negative) makes range [-315, 45). Multiplying by 1/3 # (for cube root) gives range [-105, 15). In particular # the range [-90, 180] in zeta space maps to [-90, 0] in # w space as required. r = cbrt(r * self._3_mv_e) a = atan2(d - psi, psi + d) / 3.0 - PI_4 s, c = sincos2(a) u = r * c v = r * s + self._Ev.cK else: # Use spherical TM, Lee 12.6 -- writing C{atanh(sin(lam) / # cosh(psi)) = asinh(sin(lam) / hypot(cos(lam), sinh(psi)))}. # This takes care of the log singularity at C{zeta = Eu.K()}, # corresponding to the north pole. s, c = sincos2(lam) h, r = sinh(psi), self._Eu_cK_PI_2 # But scale to put 90, 0 on the right place u = r * atan2(h, c) v = r * asinh(s / hypot(c, h)) return u, v, trip
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 _sncndn3(self, phi): '''(INTERNAL) Helper for C{.fEinv} and C{._fXf}. ''' sn, cn = sincos2(phi) return sn, cn, self.fDelta(sn, cn)
def toOsgr(latlon, lon=None, datum=Datums.WGS84, Osgr=Osgr, name=''): '''Convert a lat-/longitude point to an OSGR coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @keyword lon: Optional longitude in degrees (scalar or C{None}). @keyword datum: Optional datum to convert (C{Datum}). @keyword Osgr: Optional (sub-)class to return the OSGR coordinate (L{Osgr}) or C{None}. @keyword name: Optional B{C{Osgr}} name (C{str}). @return: The OSGR coordinate (B{C{Osgr}}) or an L{EasNor2Tuple}C{(easting, northing)} if B{C{Osgr}} is C{None}. @raise TypeError: Non-ellipsoidal B{C{latlon}} or B{C{datum}} conversion failed. @raise OSGRError: Invalid B{C{latlon}} or B{C{lon}}. @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('%s not %s: %r' % ('lon', None, lon)) elif not name: # use latlon.name name = nameof(latlon) E = _OSGB36.ellipsoid ll = _ll2datum(latlon, _OSGB36, 'latlon') a, b = map1(radians, ll.lat, ll.lon) sa, ca = sincos2(a) 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) 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. 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 (sub-)class to return the point (C{LatLon}) or C{None}. @keyword datum: Optional datum to use (C{Datum}). @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 if B{C{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 = self.datum.ellipsoid # _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 _ in range(_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 = self.classname, self.toStr(prec=-3), 'toLatLon' raise OSGRError('no convergence %s(%s).%s' % 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, ta, ta), 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) self._latlon = _LLEB(degrees90(a), degrees180(b), datum=self.datum, name=self.name) return self._latlon3(LatLon, datum)
def intersection(start1, end1, start2, end2, height=None, wrap=False, LatLon=LatLon, **LatLon_kwds): '''Compute the intersection point of two paths both defined by two points or a start point and bearing from North. @arg start1: Start point of the first path (L{LatLon}). @arg end1: End point ofthe 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 for the intersection point, overriding the mean height (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @kwarg LatLon: Optional class to return the intersection point (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: The intersection point (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. An alternate intersection point might be the L{antipode} to the returned result. @raise IntersectionError: Intersection is ambiguous or infinite or the paths are coincident, colinear or parallel. @raise TypeError: A B{C{start}} or B{C{end}} point not L{LatLon}. @raise ValueError: Invalid B{C{height}}. @example: >>> p = LatLon(51.8853, 0.2545) >>> s = LatLon(49.0034, 2.5735) >>> i = intersection(p, 108.547, s, 32.435) # '50.9078°N, 004.5084°E' ''' _T00.others(start1, name=_start1_) _T00.others(start2, name=_start2_) hs = [start1.height, start2.height] a1, b1 = start1.philam a2, b2 = start2.philam db, b2 = unrollPI(b1, b2, wrap=wrap) r12 = vincentys_(a2, a1, db) if abs(r12) < EPS: # [nearly] coincident points a, b = favg(a1, a2), favg(b1, b2) # see <https://www.EdWilliams.org/avform.htm#Intersection> elif isscalar(end1) and isscalar(end2): # both bearings sa1, ca1, sa2, ca2, sr12, cr12 = sincos2(a1, a2, r12) x1, x2 = (sr12 * ca1), (sr12 * ca2) if abs(x1) < EPS or abs(x2) < EPS: raise IntersectionError(start1=start1, end1=end1, start2=start2, end2=end2, txt='parallel') # handle domain error for equivalent longitudes, # see also functions asin_safe and acos_safe at # <https://www.EdWilliams.org/avform.htm#Math> t1, t2 = map1(acos1, (sa2 - sa1 * cr12) / x1, (sa1 - sa2 * cr12) / x2) if sin(db) > 0: t12, t21 = t1, PI2 - t2 else: t12, t21 = PI2 - t1, t2 t13, t23 = map1(radiansPI2, end1, end2) x1, x2 = map1(wrapPI, t13 - t12, # angle 2-1-3 t21 - t23) # angle 1-2-3 sx1, cx1, sx2, cx2 = sincos2(x1, x2) if sx1 == 0 and sx2 == 0: # max(abs(sx1), abs(sx2)) < EPS raise IntersectionError(start1=start1, end1=end1, start2=start2, end2=end2, txt='infinite') sx3 = sx1 * sx2 # if sx3 < 0: # raise IntersectionError(start1=start1, end1=end1, # start2=start2, end2=end2, txt=_ambiguous_) x3 = acos1(cr12 * sx3 - cx2 * cx1) r13 = atan2(sr12 * sx3, cx2 + cx1 * cos(x3)) a, b = _destination2(a1, b1, r13, t13) # choose antipode for opposing bearings if _xb(a1, b1, end1, a, b, wrap) < 0 or \ _xb(a2, b2, end2, a, b, wrap) < 0: a, b = antipode_(a, b) # PYCHOK PhiLam2Tuple else: # end point(s) or bearing(s) x1, d1 = _x3d2(start1, end1, wrap, _1_, hs) x2, d2 = _x3d2(start2, end2, wrap, _2_, hs) x = x1.cross(x2) if x.length < EPS: # [nearly] colinear or parallel paths raise IntersectionError(start1=start1, end1=end1, start2=start2, end2=end2, txt=_colinear_) a, b = x.philam # choose intersection similar to sphericalNvector d1 = _xdot(d1, a1, b1, a, b, wrap) if d1: d2 = _xdot(d2, a2, b2, a, b, wrap) if (d2 < 0 and d1 > 0) or (d2 > 0 and d1 < 0): a, b = antipode_(a, b) # PYCHOK PhiLam2Tuple h = fmean(hs) if height is None else Height(height) return _latlon3(degrees90(a), degrees180(b), h, intersection, LatLon, **LatLon_kwds)