def meanOf(points, height=None, LatLon=LatLon): '''Compute the geographic mean of several points. @param points: Points to be averaged (L{LatLon}[]). @keyword height: Optional height at mean point, overriding the mean height (C{meter}). @keyword LatLon: Optional (sub-)class to return the mean point (L{LatLon}) or C{None}. @return: Point at geographic mean and height (L{LatLon}) or 3-tuple (C{degrees90}, C{degrees180}, height) if I{LatLon} is C{None}. @raise TypeError: Some I{points} are not L{LatLon}. @raise ValueError: No I{points}. ''' # geographic mean n, points = _Trll.points2(points, closed=False) m = sumOf(points[i].toVector3d() for i in range(n)) a, b = m.to2ll() if height is None: h = fmean(points[i].height for i in range(n)) else: h = height return (a, b, h) if LatLon is None else LatLon(a, b, height=h)
def meanOf(points, height=None, LatLon=LatLon): '''Compute the geographic mean of several points. @param points: Points to be averaged (L{LatLon}[]). @keyword height: Optional height at mean point, overriding the mean height (C{meter}). @keyword LatLon: Optional (sub-)class to return the mean point (L{LatLon}) or C{None}. @return: Point at geographic mean and height (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. @raise TypeError: Some B{C{points}} are not L{LatLon}. @raise ValueError: No B{C{points}}. ''' # geographic mean n, points = _Trll.points2(points, closed=False) m = sumOf(points[i].toVector3d() for i in range(n)) a, b = m.to2ll() if height is None: h = fmean(points[i].height for i in range(n)) else: h = height r = LatLon3Tuple(a, b, h) if LatLon is None else \ LatLon(a, b, height=h) return _xnamed(r, meanOf.__name__)
def meanOf(points, height=None, LatLon=LatLon): '''Compute the geographic mean of the supplied points. @param points: Points to be averaged (L{LatLon}[]). @keyword height: Optional height at mean point overriding the mean height (meter). @keyword LatLon: Optional LatLon class to return mean point (L{LatLon}) or None. @return: Point at geographic mean and height (L{LatLon}) or 3-tuple (mean_lat, mean_lon, height) if I{LatLon} is None. @raise TypeError: Some I{points} are not L{LatLon}. @raise ValueError: No I{points}. ''' # geographic mean n, points = _Trll.points(points, closed=False) m = sumOf(points[i].toVector3d() for i in range(n)) a, b = m.to2ll() if height is None: h = fmean(points[i].height for i in range(n)) else: h = height return (a, b, h) if LatLon is None else LatLon(a, b, height=h)
def intersection(start1, end1, start2, end2, height=None, LatLon=LatLon): '''Locate the intersection of two paths each defined by two points or by a start point and an initial bearing. @param start1: Start point of the first path (L{LatLon}). @param end1: End point of the 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 at the intersection point, overriding the mean height (C{meter}). @keyword LatLon: Optional (sub-)class to return the intersection point (L{LatLon}). @return: The intersection point (B{C{LatLon}}) or 3-tuple (C{degrees90}, C{degrees180}, height) if B{C{LatLon}} is C{None} or C{None} if no unique intersection exists. @raise TypeError: If B{C{start*}} or B{C{end*}} is not L{LatLon}. @raise ValueError: Intersection is ambiguous or infinite or the paths are parallel, coincident or null. @example: >>> p = LatLon(51.8853, 0.2545) >>> q = LatLon(49.0034, 2.5735) >>> i = intersection(p, 108.55, q, 32.44) # 50.9076°N, 004.5086°E ''' _Nvll.others(start1, name='start1') _Nvll.others(start2, name='start2') # If gc1 and gc2 are great circles through start and end points # (or defined by start point and bearing), then the candidate # intersections are simply gc1 × gc2 and gc2 × gc1. Most of the # work is deciding the correct intersection point to select! If # bearing is given, that determines the intersection, but if both # paths are defined by start/end points, take closer intersection. gc1, s1, e1 = _Nvll._gc3(start1, end1, 'end1') gc2, s2, e2 = _Nvll._gc3(start2, end2, 'end2') hs = start1.height, start2.height # there are two (antipodal) candidate intersection # points ... we have to choose the one to return i1 = gc1.cross(gc2, raiser='paths') # postpone computing i2 until needed # i2 = gc2.cross(gc1, raiser='paths') # selection of intersection point depends on how # paths are defined (by bearings or endpoints) if e1 and e2: # endpoint+endpoint d = sumOf((s1, s2, e1, e2)).dot(i1) hs += end1.height, end2.height elif e1 and not e2: # endpoint+bearing # gc2 x v2 . i1 +ve means v2 bearing points to i1 d = gc2.cross(s2).dot(i1) hs += end1.height, elif e2 and not e1: # bearing+endpoint # gc1 x v1 . i1 +ve means v1 bearing points to i1 d = gc1.cross(s1).dot(i1) hs += end2.height, else: # bearing+bearing # if gc x v . i1 is +ve, initial bearing is # towards i1, otherwise towards antipodal i2 d1 = gc1.cross(s1).dot(i1) # +ve means p1 bearing points to i1 d2 = gc2.cross(s2).dot(i1) # +ve means p2 bearing points to i1 if d1 > 0 and d2 > 0: d = 1 # both point to i1 elif d1 < 0 and d2 < 0: d = -1 # both point to i2 else: # d1, d2 opposite signs # intersection is at further-away intersection # point, take opposite intersection from mid- # point of v1 and v2 [is this always true?] d = -s1.plus(s2).dot(i1) h = fmean(hs) if height is None else height i = i1 if d > 0 else gc2.cross(gc1, raiser='paths') return i.toLatLon(height=h, LatLon=LatLon) # Nvector(i.x, i.y, i.z).toLatLon(...)
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(...)
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__)