def toOsgr(latlon, lon=None, datum=Datums.WGS84, Osgr=Osgr): '''Convert a lat-/longitude point to an OSGR coordinate. @param latlon: Latitude (degrees) or an (ellipsoidal) geodetic I{LatLon} point. @keyword lon: Optional longitude in degrees (scalar or None). @keyword datum: Optional datum to convert (I{Datum}). @keyword Osgr: Optional Osgr class to use for the OSGR coordinate (L{Osgr}). @return: The OSGR coordinate (L{Osgr}). @raise TypeError: If I{latlon} is not ellipsoidal or if I{datum} conversion failed. @raise ValueError: Invalid I{latlon} or I{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, _eLLb): # XXX fix failing _eLLb.convertDatum() latlon = _eLLb(*parseDMS2(latlon, lon), datum=datum) elif lon is not None: raise ValueError('%s not %s: %r' % ('lon', None, lon)) E = _OSGB36.ellipsoid ll = _ll2datum(latlon, _OSGB36, 'latlon') a, b = map1(radians, ll.lat, ll.lon) ca, sa, ta = cos(a), sin(a), tan(a) s = E.e2s2(sa) v = E.a * _F0 / sqrt(s) # nu r = s / E.e12 # nu / rho == v / (v * E.e12 / s) x2 = r - 1 # η2 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) return Osgr(e, n)
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 _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 _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 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 I{radius}). @keyword radius: Optional mean earth radius (C{meter}). @return: Height (C{meter}, same units as I{distance} and I{radius}). @raise ValueError: Invalid I{angle}, I{distance} or I{radius}. @see: U{MultiDop GeogBeamHt<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>}). ''' 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 _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 _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 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 Bowring’s (1985) formulation for μm precision in concise form: U{'The accuracy of geodetic latitude and height equations' <http://www.researchgate.net/publication/ 233668213_The_Accuracy_of_Geodetic_Latitude_and_Height_Equations>}, B. R. Bowring, Survey Review, Vol 28, 218, Oct 1985. See also Ralph M. Toms U{'An Efficient Algorithm for Geocentric to Geodetic Coordinate Conversion' <http://www.osti.gov/scitech/biblio/110235>}, Sept 1995 and U{'An Improved Algorithm for Geocentric to Geodetic Coordinate Conversion'<http://www.osti.gov/scitech/servlets/purl/231228>}, Apr 1996, from Lawrence Livermore National Laboratory. @keyword datum: Optional datum to use (L{Datum}). @return: 3-Tuple (lat, lon, heigth) in (degrees90, degrees180, meter). ''' 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) ca, sa = cos(a), sin(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 <http://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 a, b, h
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 and <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 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 rhumbMidpointTo(self, other, height=None): '''Return the (loxodromic) midpoint between this and an other point. @param other: The other point (spherical LatLon). @keyword height: Optional height, overriding the mean height (C{meter}). @return: The midpoint (spherical C{LatLon}). @raise TypeError: The I{other} point is not spherical. @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.to2ab() a2, b2 = other.to2ab() 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 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 I{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 I{radius}). @keyword radius: Optional mean earth radius (C{meter}). @keyword refraction: Consider atmospheric refraction (C{bool}). @return: Distance (C{meter}, same units as I{height} and I{radius}). @raise ValueError: Invalid I{height} or I{radius}. @see: U{Distance to horizon<http://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 trilaterate(point1, distance1, point2, distance2, point3, distance3, radius=R_M, height=None, LatLon=LatLon, useZ=False): '''Locate a point at given distances from three other points. See also U{Trilateration<https://WikiPedia.org/wiki/Trilateration>}. @param point1: First point (L{LatLon}). @param distance1: Distance to the first point (C{meter}, same units as B{C{radius}}). @param point2: Second point (L{LatLon}). @param distance2: Distance to the second point (C{meter}, same units as B{C{radius}}). @param point3: Third point (L{LatLon}). @param distance3: Distance to the third point (C{meter}, same units as B{C{radius}}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword height: Optional height at the trilaterated point, overriding the IDW height (C{meter}, same units as B{C{radius}}). @keyword LatLon: Optional (sub-)class to return the trilaterated point (L{LatLon}). @keyword useZ: Include Z component iff non-NaN, non-zero (C{bool}). @return: Trilaterated point (B{C{LatLon}}). @raise TypeError: If B{C{point1}}, B{C{point2}} or B{C{point3}} is not L{LatLon}. @raise ValueError: Invalid B{C{radius}}, some B{C{distances}} exceed trilateration or some B{C{points}} coincide. ''' def _nd2(p, d, name, *qs): # return Nvector and radial distance squared _Nvll.others(p, name=name) for q in qs: if p.isequalTo(q, EPS): raise ValueError('%s %s: %r' % ('coincident', 'points', p)) return p.toNvector(), (float(d) / radius)**2 if float(radius or 0) < EPS: raise ValueError('%s %s: %r' % ('radius', 'invalid', radius)) n1, d12 = _nd2(point1, distance1, 'point1') n2, d22 = _nd2(point2, distance2, 'point2', point1) n3, d32 = _nd2(point3, distance3, 'point3', point1, point2) # the following uses x,y coordinate system with origin at n1, x axis n1->n2 y = n3.minus(n1) x = n2.minus(n1) d = x.length # distance n1->n2 if d > EPS_2: # and y.length > EPS_2: X = x.unit() # unit vector in x direction n1->n2 i = X.dot(y) # signed magnitude of x component of n1->n3 Y = y.minus(X.times(i)).unit() # unit vector in y direction j = Y.dot(y) # signed magnitude of y component of n1->n3 if abs(j) > EPS_2: # courtesy Carlos Freitas <https://GitHub.com/mrJean1/PyGeodesy/issues/33> x = fsum_(d12, -d22, d**2) / (2 * d) # n1->intersection x- and ... y = fsum_(d12, -d32, i**2, j**2) / (2 * j) - (x * i / j ) # ... y-component n = n1.plus(X.times(x)).plus(Y.times(y)) # .plus(Z.times(z)) if useZ: # include non-NaN, non-zero Z component z = fsum_(d12, -(x**2), -(y**2)) if z > EPS: Z = X.cross(Y) # unit vector perpendicular to plane n = n.plus(Z.times(sqrt(z))) if height is None: h = fidw((point1.height, point2.height, point3.height), map1(fabs, distance1, distance2, distance3)) else: h = height return n.toLatLon( height=h, LatLon=LatLon) # Nvector(n.x, n.y, n.z).toLatLon(...) # no intersection, d < EPS_2 or j < EPS_2 raise ValueError('no %s for %r, %r, %r at %r, %r, %r' % ('trilaterate', point1, point2, point3, distance1, distance2, distance3))
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 I{Osgr} name (C{str}). @return: The OSGR coordinate (L{Osgr}) or 2-tuple (easting, northing) if I{Osgr} is C{None}. @raise TypeError: Non-ellipsoidal I{latlon} or I{datum} conversion failed. @raise ValueError: Invalid I{latlon} or I{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 ValueError('%s not %s: %r' % ('lon', None, lon)) elif not name: # use latlon.name name = _nameof(latlon) or name # PYCHOK no effect E = _OSGB36.ellipsoid ll = _ll2datum(latlon, _OSGB36, 'latlon') a, b = map1(radians, ll.lat, ll.lon) sa, ca = sincos2(a) s = E.e2s2(sa) v = E.a * _F0 / sqrt(s) # nu r = s / E.e12 # nu / rho == v / (v * E.e12 / s) 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) return (e, n) if Osgr is None else _xnamed(Osgr(e, n), 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 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 trilaterate(point1, distance1, point2, distance2, point3, distance3, radius=R_M, height=None, LatLon=LatLon): '''Locate a point at given distances from three other points. See also U{Trilateration<http://WikiPedia.org/wiki/Trilateration>}. @param point1: First point (L{LatLon}). @param distance1: Distance to the first point (C{meter}, same units as I{radius}). @param point2: Second point (L{LatLon}). @param distance2: Distance to the second point (C{meter}, same units as I{radius}). @param point3: Third point (L{LatLon}). @param distance3: Distance to the third point (C{meter}, same units as I{radius}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword height: Optional height at the trilaterated point, overriding the mean height (C{meter}, same units as I{radius}). @keyword LatLon: Optional (sub-)class for the trilaterated point (L{LatLon}). @return: Trilaterated point (L{LatLon}). @raise TypeError: One of the I{points} is not L{LatLon}. @raise ValueError: Invalid I{radius}, some I{distances} exceed trilateration or some I{points} coincide. ''' def _nd2(p, d, name, *qs): # return Nvector and radial distance squared _Nvll.others(p, name=name) for q in qs: if p.isequalTo(q, EPS): raise ValueError('%s %s: %r' % ('coincident', 'points', p)) return p.toNvector(), (float(d) / radius)**2 if float(radius or 0) < EPS: raise ValueError('%s %s: %r' % ('radius', 'invalid', radius)) n1, d12 = _nd2(point1, distance1, 'point1') n2, d22 = _nd2(point2, distance2, 'point2', point1) n3, d32 = _nd2(point3, distance3, 'point3', point1, point2) # the following uses x,y coordinate system with origin at n1, x axis n1->n2 x = n2.minus(n1) y = n3.minus(n1) z = 0 d = x.length # distance n1->n2 if d > EPS: # and (y.length * 2) > EPS: X = x.unit() # unit vector in x direction n1->n2 i = X.dot(y) # signed magnitude of x component of n1->n3 Y = y.minus(X.times(i)).unit() # unit vector in y direction j = Y.dot(y) # signed magnitude of y component of n1->n3 if abs(j) > EPS: x = fsum_(d12, -d22, d**2) / d # n1->intersection x- and ... y = fsum_(d12, -d32, i**2, j**2, -2 * x * i) / j # ... y-component z = (x**2 + y**2) * 0.25 # z = sqrt(d12 - z) # z will be NaN for no intersections if not 0 < z < d12: raise ValueError('no %s for %r, %r, %r at %r, %r, %r' % ('trilaterate', point1, point2, point3, distance1, distance2, distance3)) # Z = X.cross(Y) # unit vector perpendicular to plane # note don't use Z component; assume points at same height n = n1.plus(X.times(x)).plus(Y.times(y)) # .plus(Z.times(z)) if height is None: h = fmean((point1.height, point2.height, point3.height)) else: h = height return n.toLatLon(height=h, LatLon=LatLon) # Nvector(n.x, n.y, n.z).toLatLon(...)