def crossingParallels(self, other, lat, wrap=False): '''Return the pair of meridians at which a great circle defined by this and an other point crosses the given latitude. @param other: The other point defining great circle (L{LatLon}). @param lat: Latitude at the crossing (C{degrees}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: 2-Tuple (lon1, lon2) in (C{degrees180}) or C{None} if the great circle doesn't reach the given I{lat}. ''' self.others(other) a1, b1 = self.to2ab() a2, b2 = other.to2ab() a = radians(lat) db, b2 = unrollPI(b1, b2, wrap=wrap) ca, ca1, ca2, cdb = map1(cos, a, a1, a2, db) sa, sa1, sa2, sdb = map1(sin, a, a1, a2, db) x = sa1 * ca2 * ca * sdb y = sa1 * ca2 * ca * cdb - ca1 * sa2 * ca z = ca1 * ca2 * sa * sdb h = hypot(x, y) if h < EPS or abs(z) > h: return None # great circle doesn't reach latitude m = atan2(-y, x) + b1 # longitude at max latitude d = acos1(z / h) # delta longitude to intersections return degrees180(m - d), degrees180(m + d)
def initialBearingTo(self, other, wrap=False): '''Compute the initial bearing (aka forward azimuth) from this to an other point. @param other: The other point (L{LatLon}). @keyword wrap: Wrap and unroll longitudes (bool). @return: Initial bearing (compass degrees). @raise TypeError: The I{other} point is not L{LatLon}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351) >>> b = p1.initialBearingTo(p2) # 156.2 @JSname: I{bearingTo}. ''' self.others(other) a1, b1 = self.to2ab() a2, b2 = other.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) ca1, ca2, cdb = map1(cos, a1, a2, db) sa1, sa2, sdb = map1(sin, a1, a2, db) # see <http://mathforum.org/library/drmath/view/55417.html> x = ca1 * sa2 - sa1 * ca2 * cdb y = sdb * ca2 return degrees360(atan2(y, x))
def bearing_(a1, b1, a2, b2, final=False, wrap=False): '''Compute the initial or final bearing (forward or reverse azimuth) between a (spherical) start and end point. @param a1: Start latitude (C{radians}). @param b1: Start longitude (C{radians}). @param a2: End latitude (C{radians}). @param b2: End longitude (C{radians}). @keyword final: Return final bearing if C{True}, initial otherwise (C{bool}). @keyword wrap: Wrap and L{unrollPI} longitudes (C{bool}). @return: Initial or final bearing (compass C{radiansPI2}) or zero if start and end point coincide. ''' if final: a1, b1, a2, b2 = a2, b2, a1, b1 r = PI + PI2 else: r = PI2 db, _ = unrollPI(b1, b2, wrap=wrap) ca1, ca2, cdb = map1(cos, a1, a2, db) sa1, sa2, sdb = map1(sin, a1, a2, db) # see <http://MathForum.org/library/drmath/view/55417.html> x = ca1 * sa2 - sa1 * ca2 * cdb y = sdb * ca2 return (atan2(y, x) + r) % PI2 # wrapPI2
def _x3d2(start, end, wrap, n): # see <http://www.EdWilliams.org/intersect.htm> (5) ff a1, b1 = start.to2ab() if isscalar(end): # bearing, make a point a2, b2 = _destination2_(a1, b1, PI_4, radians(end)) else: # must be a point _Trll.others(end, name='end' + n) a2, b2 = end.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) if max(abs(db), abs(a2 - a1)) < EPS: raise ValueError('intersection %s%s null: %r' % ('path', n, (start, end))) # note, in EdWilliams.org/avform.htm W is + and E is - b21, b12 = db * 0.5, -(b1 + b2) * 0.5 cb21, cb12 = map1(cos, b21, b12) sb21, sb12 = map1(sin, b21, b12) sa21, sa12 = map1(sin, a1 - a2, a1 + a2) x = Vector3d(sa21 * sb12 * cb21 - sa12 * cb12 * sb21, sa21 * cb12 * cb21 + sa12 * sb12 * sb21, cos(a1) * cos(a2) * sin(db), ll=start) return x.unit(), (db, (a2 - a1)) # negated d
def greatCircle(self, bearing): '''Compute the vector normal to great circle obtained by heading on the given initial bearing from this point. Direction of vector is such that initial bearing vector b = c × n, where n is an n-vector representing this point. @param bearing: Bearing from this point (compass C{degrees360}). @return: Vector representing great circle (L{Vector3d}). @example: >>> p = LatLon(53.3206, -1.7297) >>> g = p.greatCircle(96.0) >>> g.toStr() # (-0.794, 0.129, 0.594) ''' a, b = self.to2ab() t = radians(bearing) ca, cb, ct = map1(cos, a, b, t) sa, sb, st = map1(sin, a, b, t) return Vector3d(sb * ct - cb * sa * st, -cb * ct - sb * sa * st, ca * st) # XXX .unit()?
def intermediateTo(self, other, fraction, height=None, wrap=False): '''Locate the point at given fraction between this and an other point. @param other: The other point (L{LatLon}). @param fraction: Fraction between both points (float, 0.0 = this point, 1.0 = the other point). @keyword height: Optional height, overriding the fractional height (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Intermediate point (L{LatLon}). @raise TypeError: The I{other} point is not L{LatLon}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351) >>> p = p1.intermediateTo(p2, 0.25) # 51.3721°N, 000.7073°E @JSname: I{intermediatePointTo}. ''' self.others(other) a1, b1 = self.to2ab() a2, b2 = other.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) r = haversine_(a2, a1, db) sr = sin(r) if abs(sr) > EPS: cb1, cb2, ca1, ca2 = map1(cos, b1, b2, a1, a2) sb1, sb2, sa1, sa2 = map1(sin, b1, b2, a1, a2) A = sin((1 - fraction) * r) / sr B = sin(fraction * r) / sr x = A * ca1 * cb1 + B * ca2 * cb2 y = A * ca1 * sb1 + B * ca2 * sb2 z = A * sa1 + B * sa2 a = atan2(z, hypot(x, y)) b = atan2(y, x) else: # points too close a = favg(a1, a2, f=fraction) b = favg(b1, b2, f=fraction) if height is None: h = self._havg(other, f=fraction) else: h = height return self.classof(degrees90(a), degrees180(b), height=h)
def cross(self, other, raiser=None): '''Compute the cross product of this and an other vector. @param other: The other vector (L{Vector3d}). @keyword raiser: Optional, L{CrossError} label if raised (C{str}). @return: Cross product (L{Vector3d}). @raise CrossError: Zero or near-zero cross product and both I{raiser} and L{crosserrors} set. @raise TypeError: Incompatible I{other} C{type}. @raise ValueError: Coincident or colinear to I{other}. ''' self.others(other) x = self.y * other.z - self.z * other.y y = self.z * other.x - self.x * other.z z = self.x * other.y - self.y * other.x if raiser and self.crosserrors and max(map1(abs, x, y, z)) < EPS: t = 'coincident' if self.isequalTo(other) else 'colinear' r = getattr(other, '_fromll', None) or other raise CrossError('%s %s: %r' % (t, raiser, r)) return self.classof(x, y, z)
def __init__(self, x, y, radius=R_MA, name=''): '''New Web Mercator (WM) coordinate. @param x: Easting from central meridian (C{meter}). @param y: Northing from equator (C{meter}). @keyword radius: Optional earth radius (C{meter}). @keyword name: Optional name (C{str}). @raise ValueError: Invalid I{x}, I{y} or I{radius}. @example: >>> import pygeodesy >>> w = pygeodesy.Wm(448251, 5411932) ''' if name: self.name = name try: self._x, self._y, r = map1(float, x, y, radius) except (TypeError, ValueError): raise ValueError('%s invalid: %r' % (Wm.__name__, (x, y, radius))) if r < EPS: # check radius t = '%s.%s' % (classname(self), 'radius') raise ValueError('%s invalid: %r' % (t, r)) self._radius = r
def _xyhs3(atype, m, knots, off=True): # convert knot C{LatLon}s to tuples or C{NumPy} arrays and C{SciPy} sphericals xs, ys, hs = zip(*_xyhs(knots, off=off)) # PYCHOK unzip n = len(hs) if n < m: raise HeightError('insufficient %s: %s, need %s' % ('knots', n, m)) return map1(atype, xs, ys, hs)
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 to2ab(self): '''Return this point's lat- and longitude in radians. @return: 2-Tuple (lat, lon) in (C{radiansPI_2}, C{radiansPI}). ''' if not self._ab: self._ab = map1(radians, self.lat, self.lon) return self._ab
def _exs(n, points): # iterate over spherical edge excess a1, b1 = points[n - 1].to2ab() ta1 = tan_2(a1) for i in range(n): a2, b2 = points[i].to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) ta2, tdb = map1(tan_2, a2, db) yield atan2(tdb * (ta1 + ta2), 1 + ta1 * ta2) ta1, b1 = ta2, b2
def to2ab(self): '''Return this point's lat- and longitude in C{radians}. @return: A L{PhiLam2Tuple}C{(phi, lambda)}. ''' if not self._ab: a, b = map1(radians, self.lat, self.lon) self._ab = self._xnamed(PhiLam2Tuple(a, b)) return self._ab
def _destination2_(a, b, r, t): '''(INTERNAL) Computes destination lat- and longitude. @param a: Latitude (C{radians}). @param b: Longitude (C{radians}). @param r: Angular distance (C{radians}). @param t: Bearing (compass C{radians}). @return: 2-Tuple (lat, lon) of (radians, radians). ''' # see <http://www.EdWilliams.org/avform.htm#LL> ca, cr, ct = map1(cos, a, r, t) sa, sr, st = map1(sin, a, r, t) a = asin(ct * sr * ca + cr * sa) d = atan2(st * sr * ca, cr - sa * sin(a)) # note, in EdWilliams.org/avform.htm W is + and E is - return a, b + d
def _destination2(a, b, r, t): '''(INTERNAL) Computes destination lat-/longitude. @param a: Latitude (C{radians}). @param b: Longitude (C{radians}). @param r: Angular distance (C{radians}). @param t: Bearing (compass C{radians}). @return: 2-Tuple (lat, lon) of (C{degrees90}, C{degrees180}). ''' # see <http://www.EdWilliams.org/avform.htm#LL> ca, cr, ct = map1(cos, a, r, t) sa, sr, st = map1(sin, a, r, t) a = asin(ct * sr * ca + cr * sa) d = atan2(st * sr * ca, cr - sa * sin(a)) # note, use addition, not subtraction of d (since # in EdWilliams.org/avform.htm W is + and E is -) return degrees90(a), degrees180(b + d)
def midpointTo(self, other, height=None, wrap=False): '''Find the midpoint between this and an other point. @param other: The other point (L{LatLon}). @keyword height: Optional height for midpoint, overriding the mean height (meter). @keyword wrap: Wrap and unroll longitudes (bool). @return: Midpoint (L{LatLon}). @raise TypeError: The I{other} point is not L{LatLon}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351) >>> m = p1.midpointTo(p2) # '50.5363°N, 001.2746°E' ''' self.others(other) # see <http://mathforum.org/library/drmath/view/51822.html> a1, b1 = self.to2ab() a2, b2 = other.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) ca1, ca2, cdb = map1(cos, a1, a2, db) sa1, sa2, sdb = map1(sin, a1, a2, db) x = ca2 * cdb + ca1 y = ca2 * sdb a = atan2(sa1 + sa2, hypot(x, y)) b = atan2(y, x) + b1 if height is None: h = self._havg(other) else: h = height return self.classof(degrees90(a), degrees180(b), height=h)
def bearing(lat1, lon1, lat2, lon2, final=False, wrap=False): '''Compute the initial or final bearing (forward or reverse azimuth) between a (spherical) start and end point. @param lat1: Start latitude (C{degrees}). @param lon1: Start longitude (C{degrees}). @param lat2: End latitude (C{degrees}). @param lon2: End longitude (C{degrees}). @keyword final: Return final or initial bearing (C{bool}). @keyword wrap: Wrap and L{unroll180} longitudes (C{bool}). @return: Initial or final bearing (compass C{degrees360}) or zero if start and end point coincide. ''' a1, b1, a2, b2 = map1(radians, lat1, lon1, lat2, lon2) return degrees(bearing_(a1, b1, a2, b2, final=final, wrap=wrap))
def bearing(lat1, lon1, lat2, lon2, **options): '''Compute the initial or final bearing (forward or reverse azimuth) between a (spherical) start and end point. @param lat1: Start latitude (C{degrees}). @param lon1: Start longitude (C{degrees}). @param lat2: End latitude (C{degrees}). @param lon2: End longitude (C{degrees}). @keyword options: Optional keyword arguments for function L{bearing_}. @return: Initial or final bearing (compass C{degrees360}) or zero if start and end point coincide. ''' ab4 = map1(radians, lat1, lon1, lat2, lon2) return degrees(bearing_(*ab4, **options))
def __init__(self, x, y, radius=R_MA): '''New Web Mercator (WM) coordinate. @param x: Easting from central meridian (meter). @param y: Northing from equator (meter). @keyword radius: Optional earth radius (meter). @raise ValueError: Invalid I{x}, I{y} or I{radius}. @example: >>> import pygeodesy >>> w = pygeodesy.Wm(448251, 5411932) ''' try: self._x, self._y, self._radius = map1(float, x, y, radius) except (TypeError, ValueError): raise ValueError('%s invalid: %r' % (Wm.__name__, (x, y, radius))) _ = self.radius # PYCHOK check radius
def areaOf(points, radius=R_M, wrap=True): '''Calculate the area of a (spherical) polygon (with great circle arcs joining the points). @param points: The polygon points (L{LatLon}[]). @keyword radius: Optional, mean earth radius (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Polygon area (C{meter}, same units as I{radius}, squared). @raise TypeError: Some I{points} are not L{LatLon}. @raise ValueError: Insufficient number of I{points}. @note: The area is based on Karney's U{'Area of a spherical polygon' <http://osgeo-org.1560.x6.nabble.com/Area-of-a-spherical-polygon-td3841625.html>}. @see: L{pygeodesy.areaOf}, L{sphericalNvector.areaOf} and L{ellipsoidalKarney.areaOf}. @example: >>> b = LatLon(45, 1), LatLon(45, 2), LatLon(46, 2), LatLon(46, 1) >>> areaOf(b) # 8666058750.718977 >>> c = LatLon(0, 0), LatLon(1, 0), LatLon(0, 1) >>> areaOf(c) # 6.18e9 ''' n, points = _Trll.points2(points, closed=True) # Area method due to Karney: for each edge of the polygon, # # tan(Δλ/2) · (tan(φ1/2) + tan(φ2/2)) # tan(E/2) = ------------------------------------ # 1 + tan(φ1/2) · tan(φ2/2) # # where E is the spherical excess of the trapezium obtained by # extending the edge to the equator-circle vector for each edge if iterNumpy2(points): def _exs(n, points): # iterate over spherical edge excess a1, b1 = points[n - 1].to2ab() ta1 = tan_2(a1) for i in range(n): a2, b2 = points[i].to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) ta2, tdb = map1(tan_2, a2, db) yield atan2(tdb * (ta1 + ta2), 1 + ta1 * ta2) ta1, b1 = ta2, b2 s = fsum(_exs(n, points)) * 2 else: a1, b1 = points[n - 1].to2ab() s, ta1 = [], tan_2(a1) for i in range(n): a2, b2 = points[i].to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) ta2, tdb = map1(tan_2, a2, db) s.append(atan2(tdb * (ta1 + ta2), 1 + ta1 * ta2)) ta1, b1 = ta2, b2 s = fsum(s) * 2 if isPoleEnclosedBy(points): s = abs(s) - PI2 return abs(s * radius**2)
def intersection(start1, bearing1, start2, bearing2, height=None, wrap=False, LatLon=LatLon): '''Compute the intersection point of two paths each defined by a start point and an initial bearing. @param start1: Start point of first path (L{LatLon}). @param bearing1: Initial bearing from start1 (compass C{degrees360}). @param start2: Start point of second path (L{LatLon}). @param bearing2: Initial bearing from start2 (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 for the intersection point (L{LatLon}) or C{None}. @return: Intersection point (L{LatLon}) or 3-tuple (C{degrees90}, C{degrees180}, height) if C{LatLon} is C{None}. @raise TypeError: Point I{start1} or I{start2} is not L{LatLon}. @raise ValueError: Intersection is ambiguous or infinite or the paths are parallel or coincident. @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') # see <http://www.EdWilliams.org/avform.htm#Intersection> 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)) else: ca1, ca2, cr12 = map1(cos, a1, a2, r12) sa1, sa2, sr12 = map1(sin, 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, start2)) # handle domain error for equivalent longitudes, # see also functions asin_safe and acos_safe at # <http://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, bearing1, bearing2) x1, x2 = map1(wrapPI, t13 - t12, # angle 2-1-3 t21 - t23) # angle 1-2-3 sx1, sx2 = map1(sin, x1, x2) if sx1 == 0 and sx2 == 0: raise ValueError('intersection %s: %r vs %r' % ('infinite', start1, start2)) sx3 = sx1 * sx2 if sx3 < 0: raise ValueError('intersection %s: %r vs %r' % ('ambiguous', start1, start2)) cx1, cx2 = map1(cos, x1, x2) x3 = acos1(cr12 * sx3 - cx2 * cx1) r13 = atan2(sr12 * sx3, cx2 + cx1 * cos(x3)) a, b = _destination2(a1, b1, r13, t13) h = start1._havg(start2) if height is None else height return (a, b, h) if LatLon is None else LatLon(a, b, height=h)
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 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 for the intersection point (L{LatLon}) or C{None}. @return: The intersection point (I{LatLon}) or 3-tuple (C{degrees90}, C{degrees180}, height) if I{LatLon} is C{None}. An alternate intersection point might be the L{antipode} to the returned result. @raise TypeError: Start or end point(s) 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') 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 <http://www.EdWilliams.org/avform.htm#Intersection> elif isscalar(end1) and isscalar(end2): # both bearings ca1, ca2, cr12 = map1(cos, a1, a2, r12) sa1, sa2, sr12 = map1(sin, 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 # <http://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, sx2 = map1(sin, 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))) cx1, cx2 = map1(cos, x1, x2) 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') x2, d2 = _x3d2(start2, end2, wrap, '2') 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 = start1._havg(start2) if height is None else height return (a, b, h) if LatLon is None else LatLon(a, b, height=h)
def isenclosedBy(point, points, wrap=False): # MCCABE 15 '''Determine whether a point is enclosed by a polygon. @param point: The point (C{LatLon} or 2-tuple (lat, lon)). @param points: The polygon points (C{LatLon}[]). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}). @return: C{True} if I{point} is inside the polygon, C{False} otherwise. @raise TypeError: Some I{points} are not C{LatLon}. @raise ValueError: Insufficient number of I{points} or invalid I{point}. @see: L{sphericalNvector.LatLon.isEnclosedBy}, L{sphericalTrigonometry.LatLon.isEnclosedBy} and U{MultiDop GeogContainPt<http://GitHub.com/NASA/MultiDop>} (U{Shapiro et al. 2009, JTECH <http://journals.AMetSoc.org/doi/abs/10.1175/2009JTECHA1256.1>} and U{Potvin et al. 2012, JTECH <http://journals.AMetSoc.org/doi/abs/10.1175/JTECH-D-11-00019.1>}). ''' try: y0, x0 = point.lat, point.lon except AttributeError: try: y0, x0 = map1(float, *point[:2]) except (IndexError, TypeError, ValueError): raise ValueError('%s invalid: %r' % ('point', point)) pts = LatLon2psxy(points, closed=True, radius=None, wrap=wrap) n = len(pts) if wrap: x0, y0 = wrap180(x0), wrap90(y0) def _dxy(x1, i): x2, y2, _ = pts[i] dx, x2 = unroll180(x1, x2, wrap=i < (n - 1)) return dx, x2, y2 else: x0 = fmod(x0, 360.0) # not x0 % 360 def _dxy(x1, i): # PYCHOK expected x, y, _ = pts[i] x %= 360.0 if x < (x0 - 180): x += 360 elif x >= (x0 + 180): x -= 360 return (x - x1), x, y e = m = False s = Fsum() _, x1, y1 = _dxy(x0, n - 1) for i in range(n): dx, x2, y2 = _dxy(x1, i) # ignore duplicate and near-duplicate pts if max(abs(dx), abs(y2 - y1)) > EPS: # determine if polygon edge (x1, y1)..(x2, y2) straddles # point (lat, lon) or is on boundary, but do not count # edges on boundary as more than one crossing if abs(dx) < 180 and (x1 < x0 <= x2 or x2 < x0 <= x1): m = not m dy = (x0 - x1) * (y2 - y1) - (y0 - y1) * dx if (dy > 0 and dx >= 0) or (dy < 0 and dx <= 0): e = not e s.fadd(sin(radians(y2))) x1, y1 = x2, y2 # An odd number of meridian crossings means, the polygon # contains a pole. Assume it is the pole on the hemisphere # containing the polygon mean point and if the polygon does # contain the North Pole, flip the result. if m and s.fsum() > 0: e = not e return e
def _T(*fs): # tuple of single floats return map1(_F, *fs)