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, **LatLon_kwds): '''Compute the geographic mean of several points. @arg points: Points to be averaged (L{LatLon}[]). @kwarg height: Optional height at mean point, overriding the mean height (C{meter}). @kwarg LatLon: Optional class to return the mean point (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=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}} or invalid B{C{height}}. ''' # geographic mean n, points = _Trll.points2(points, closed=False) m = sumOf(points[i]._N_vector for i in range(n)) lat, lon = m._N_vector.latlon if height is None: h = fmean(points[i].height for i in range(n)) else: h = Height(height) return _latlon3(lat, lon, h, meanOf, LatLon, **LatLon_kwds)
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 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 intersections2( center1, rad1, center2, rad2, radius=R_M, # MCCABE 13 height=None, wrap=False, LatLon=LatLon, **LatLon_kwds): '''Compute the intersection points of two circles each defined by a center point and radius. @arg center1: Center of the first circle (L{LatLon}). @arg rad1: Radius of the second circle (C{meter} or C{radians}, see B{C{radius}}). @arg center2: Center of the second circle (L{LatLon}). @arg rad2: Radius of the second circle (C{meter} or C{radians}, see B{C{radius}}). @kwarg radius: Mean earth radius (C{meter} or C{None} if both B{C{rad1}} and B{C{rad2}} are given in C{radians}). @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 points (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: 2-Tuple of the intersection points, each a B{C{LatLon}} instance or L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. The intersection points are the same instance for abutting circles. @raise IntersectionError: Concentric, antipodal, invalid or non-intersecting circles. @raise TypeError: If B{C{center1}} or B{C{center2}} not L{LatLon}. @raise ValueError: Invalid B{C{rad1}}, B{C{rad2}}, B{C{radius}} or B{C{height}}. @note: Courtesy U{Samuel Čavoj<https://GitHub.com/mrJean1/PyGeodesy/issues/41>}. @see: This U{Answer<https://StackOverflow.com/questions/53324667/ find-intersection-coordinates-of-two-circles-on-earth/53331953>}. ''' def _destination1(bearing): a, b = _destination2(a1, b1, r1, bearing) return _latlon3(degrees90(a), degrees180(b), h, intersections2, LatLon, **LatLon_kwds) _Trll.others(center1, name='center1') _Trll.others(center2, name='center2') a1, b1 = center1.philam a2, b2 = center2.philam r1, r2, x = _rads3(rad1, rad2, radius) if x: a1, b1, a2, b2 = a2, b2, a1, b1 db, _ = unrollPI(b1, b2, wrap=wrap) d = vincentys_(a2, a1, db) # radians if d < max(r1 - r2, EPS): raise IntersectionError(center1=center1, rad1=rad1, center2=center2, rad2=rad2, txt=_near_concentric_) x = fsum_(r1, r2, -d) if x > EPS: try: sd, cd, s1, c1, _, c2 = sincos2(d, r1, r2) x = sd * s1 if abs(x) < EPS: raise ValueError x = acos1((c2 - cd * c1) / x) except ValueError: raise IntersectionError(center1=center1, rad1=rad1, center2=center2, rad2=rad2) elif x < 0: raise IntersectionError(center1=center1, rad1=rad1, center2=center2, rad2=rad2, txt=_too_distant_) b = bearing_(a1, b1, a2, b2, final=False, wrap=wrap) if height is None: h = fmean((center1.height, center2.height)) else: Height(height) if abs(x) > EPS: return _destination1(b + x), _destination1(b - x) else: # abutting circles x = _destination1(b) return x, x
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' ''' _Trll.others(start1, name=_start1_) _Trll.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)