def radical2(distance, radius1, radius2): '''Compute the I{radical ratio} and I{radical line} of two U{intersecting circles<https://MathWorld.Wolfram.com/ Circle-CircleIntersection.html>}. The I{radical line} is perpendicular to the axis thru the centers of the circles at C{(0, 0)} and C{(B{distance}, 0)}. @arg distance: Distance between the circle centers (C{scalar}). @arg radius1: Radius of the first circle (C{scalar}). @arg radius2: Radius of the second circle (C{scalar}). @return: A L{Radical2Tuple}C{(ratio, xline)} where C{0.0 <= ratio <= 1.0} and C{xline} is along the B{C{distance}}. @raise IntersectionError: The B{C{distance}} exceeds the sum of B{C{radius1}} and B{C{radius2}}. @raise UnitError: Invalid B{C{distance}}, B{C{radius1}} or B{C{radius2}}. ''' d = Distance_(distance) r1 = Radius_(radius1=radius1) r2 = Radius_(radius2=radius2) if d > (r1 + r2): raise IntersectionError(distance=d, radius1=r1, radius2=r2, txt=_too_(_distant_)) return _radical2(d, r1, r2)
def boundsOf(self, wide, high, radius=R_M): '''Return the SE and NW lat-/longitude of a great circle bounding box centered at this location. @arg wide: Longitudinal box width (C{meter}, same units as B{C{radius}} or C{degrees} if B{C{radius}} is C{None}). @arg high: Latitudinal box height (C{meter}, same units as B{C{radius}} or C{degrees} if B{C{radius}} is C{None}). @kwarg radius: Mean earth radius (C{meter}). @return: A L{Bounds2Tuple}C{(latlonSW, latlonNE)}, the lower-left and upper-right corner (C{LatLon}). @see: U{https://www.Movable-Type.co.UK/scripts/latlong-db.html} ''' w = Scalar_(wide, name='wide') * _0_5 h = Scalar_(high, name='high') * _0_5 if radius is not None: r = Radius_(radius) c = cos(self.phi) w = degrees(asin(w / r) / c) if c > EPS else _0_0 # XXX h = degrees(h / r) w, h = abs(w), abs(h) r = Bounds2Tuple(self.classof(self.lat - h, self.lon - w, height=self.height), self.classof(self.lat + h, self.lon + w, height=self.height)) return self._xnamed(r)
def intersections2(center1, radius1, center2, radius2, sphere=True, Vector=None, **Vector_kwds): '''Compute the intersection of two spheres or circles, each defined by a center point and a radius. @arg center1: Center of the first sphere or circle (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius1: Radius of the first sphere or circle (same units as the B{C{center1}} coordinates). @arg center2: Center of the second sphere or circle (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius2: Radius of the second sphere or circle (same units as the B{C{center1}} and B{C{center2}} coordinates). @kwarg sphere: If C{True} compute the center and radius of the intersection of two spheres. If C{False}, ignore the C{z}-component and compute the intersection of two circles (C{bool}). @kwarg Vector: Class to return intersections (L{Vector3d} or C{Vector3Tuple}) or C{None} for L{Vector3d}. @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword arguments, ignored if C{B{Vector}=None}. @return: If B{C{sphere}} is C{True}, a 2-Tuple of the C{center} and C{radius} of the intersection of the spheres. The C{radius} is C{0.0} for abutting spheres. If B{C{sphere}} is C{False}, a 2-tuple of the intersection points of two circles. For abutting circles, both points are the same B{C{Vector}} instance. @raise IntersectionError: Concentric, invalid or non-intersecting spheres or circles. @raise UnitError: Invalid B{C{radius1}} or B{C{radius2}}. @see: U{Sphere-Sphere<https://MathWorld.Wolfram.com/Sphere- SphereIntersection.html>} and U{circle-circle <https://MathWorld.Wolfram.com/Circle-CircleIntersection.html>} intersections. ''' try: return _intersects2(center1, Radius_(radius1=radius1), center2, Radius_(radius2=radius2), sphere=sphere, Vector=Vector, **Vector_kwds) except (TypeError, ValueError) as x: raise IntersectionError(center1=center1, radius1=radius1, center2=center2, radius2=radius2, txt=str(x))
def _rads3(rad1, rad2, radius): # in .sphericalTrigonometry '''(INTERNAL) Convert radii to radians. ''' r1 = Radius_(rad1=rad1) r2 = Radius_(rad2=rad2) if radius is not None: # convert radii to radians r = _1_0 / Radius_(radius=radius) r1 *= r r2 *= r x = r1 < r2 if x: r1, r2 = r2, r1 if r1 > PI: raise IntersectionError(rad1=rad1, rad2=rad2, txt=_exceed_PI_radians_) return r1, r2, x
def _rads3(rad1, rad2, radius): # in .sphericalTrigonometry '''(INTERNAL) Convert radii to radians. ''' r1 = Radius_(rad1, name='rad1') r2 = Radius_(rad2, name='rad2') r = radius if r is not None: # convert radii to radians r = 1.0 / Radius_(r, name=_radius_) r1 *= r r2 *= r if r1 < r2: r1, r2, r = r2, r1, True else: r = False if r1 > PI: raise IntersectionError(rad1=rad1, rad2=rad2, txt='exceeds PI radians') return r1, r2, r
def trilaterate3d2(center1, radius1, center2, radius2, center3, radius3, eps=EPS, Vector=None, **Vector_kwds): '''Trilaterate three spheres, each given as a (3d) center point and radius. @arg center1: Center of the 1st sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius1: Radius of the 1st sphere (same C{units} as C{x}, C{y} and C{z}). @arg center2: Center of the 2nd sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius2: Radius of this sphere (same C{units} as C{x}, C{y} and C{z}). @arg center3: Center of the 3rd sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius3: Radius of the 3rd sphere (same C{units} as C{x}, C{y} and C{z}). @kwarg eps: Tolerance (C{scalar}), same units as C{x}, C{y}, and C{z}. @kwarg Vector: Class to return intersections (L{Vector3d} or C{Vector3Tuple}) or C{None} for L{Vector3d}. @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword arguments, ignored if C{B{Vector}=None}. @return: 2-Tuple with two trilaterated points, each a B{C{Vector}} instance. Both points are the same instance if all three spheres abut/intersect in a single point. @raise ImportError: Package C{numpy} not found, not installed or older than version 1.15. @raise IntersectionError: No intersection, colinear or concentric centers or trilateration failed some other way. @raise TypeError: Invalid B{C{center1}}, B{C{center2}} or B{C{center3}}. @raise UnitError: Invalid B{C{radius1}}, B{C{radius2}} or B{C{radius3}}. @note: Package U{numpy<https://pypi.org/project/numpy>} is required, version 1.15 or later. @see: Norrdine, A. U{I{An Algebraic Solution to the Multilateration Problem}<https://www.ResearchGate.net/publication/ 275027725_An_Algebraic_Solution_to_the_Multilateration_Problem>} and U{I{implementation}<https://www.ResearchGate.net/publication/ 288825016_Trilateration_Matlab_Code>}. ''' try: return _trilaterate3d2(_otherV3d(center1=center1), Radius_(radius1=radius1, low=eps), center2, radius2, center3, radius3, eps=eps, Vector=Vector, **Vector_kwds) except (AssertionError, FloatingPointError) as x: raise IntersectionError(center1=center1, radius1=radius1, center2=center2, radius2=radius2, center3=center3, radius3=radius3, txt=str(x))
def _trackDistanceTo3(self, start, end, radius, wrap): '''(INTERNAL) Helper for along-/crossTrackDistanceTo. ''' self.others(start, name='start') self.others(end, name='end') r = Radius_(radius) r = start.distanceTo(self, r, wrap=wrap) / r b = radians(start.initialBearingTo(self, wrap=wrap)) e = radians(start.initialBearingTo(end, wrap=wrap)) x = asin(sin(r) * sin(b - e)) return r, x, (e - b)
def _intersections2(center1, radius1, center2, radius2, height=None, wrap=True, equidistant=None, tol=_TOL_M, LatLon=None, **LatLon_kwds): # (INTERNAL) Iteratively compute the intersection points of two circles # each defined by an (ellipsoidal) center point and a radius, imported # by .ellipsoidalKarney and -Vincenty c1 = _xellipsoidal(center1=center1) c2 = c1.others(center2=center2) r1 = Radius_(radius1=radius1) r2 = Radius_(radius2=radius2) try: return _intersects2(c1, r1, c2, r2, height=height, wrap=wrap, equidistant=equidistant, tol=tol, LatLon=LatLon, **LatLon_kwds) except (TypeError, ValueError) as x: raise IntersectionError(center1=center1, radius1=radius1, center2=center2, radius2=radius2, txt=str(x))
def trilaterate3d2(self, radius, center2, radius2, center3, radius3, eps=EPS): '''Trilaterate this and two other spheres, each given as a (3d) center and radius. @arg radius: Radius of this sphere (same C{units} as this C{x}, C{y} and C{z}). @arg center2: Center of the 2nd sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius2: Radius of this sphere (same C{units} as this C{x}, C{y} and C{z}). @arg center3: Center of the 3rd sphere (L{Vector3d}, C{Vector3Tuple} or C{Vector4Tuple}). @arg radius3: Radius of the 3rd sphere (same C{units} as this C{x}, C{y} and C{z}). @kwarg eps: Tolerance (C{scalar}), same units as C{x}, C{y}, and C{z}. @return: 2-Tuple with two trilaterated points, each an instance of this L{Vector3d} (sub-)class. Both points are the same instance if all three spheres intersect or abut in a single point. @raise ImportError: Package C{numpy} not found, not installed or older than version 1.15. @raise IntersectionError: No intersection, colinear or near concentric centers or trilateration failed some other way. @raise TypeError: Invalid B{C{center2}} or B{C{center3}}. @raise UnitError: Invalid B{C{radius}}, B{C{radius2}} or B{C{radius3}}. @note: Package U{numpy<https://pypi.org/project/numpy>} is required, version 1.15 or later. @see: Norrdine, A. U{I{An Algebraic Solution to the Multilateration Problem}<https://www.ResearchGate.net/publication/ 275027725_An_Algebraic_Solution_to_the_Multilateration_Problem>} and U{I{implementation}<https://www.ResearchGate.net/publication/ 288825016_Trilateration_Matlab_Code>}. ''' try: return _trilaterate3d2(self if self.name else _otherV3d(center=self), Radius_(radius, low=eps), center2, radius2, center3, radius3, eps=eps, Vector=self.classof) except (AssertionError, FloatingPointError) as x: raise IntersectionError(center=self, radius=radius, center2=center2, radius2=radius2, center3=center3, radius3=radius3, txt=str(x))
def __init__(self, x, y, radius=R_MA, name=NN): '''New L{Wm} Web Mercator (WM) coordinate. @arg x: Easting from central meridian (C{meter}). @arg y: Northing from equator (C{meter}). @kwarg radius: Optional earth radius (C{meter}). @kwarg name: Optional name (C{str}). @raise WebMercatorError: Invalid B{C{x}}, B{C{y}} or B{C{radius}}. @example: >>> import pygeodesy >>> w = pygeodesy.Wm(448251, 5411932) ''' self._x = Easting(x=x, Error=WebMercatorError) self._y = Northing(y=y, Error=WebMercatorError) self._radius = Radius_(radius, Error=WebMercatorError) if name: self.name = name
def _spherical_datum(radius, name=NN, raiser=False): '''(INTERNAL) Create a L{Datum} from an L{Ellipsoid}, L{Ellipsoid2} or scalar earth C{radius}. ''' try: d = _ellipsoidal_datum(radius, name=name) except TypeError: d = None if d is None: if not isscalar(radius): _xinstanceof(Datum, Ellipsoid, Ellipsoid2, a_f2Tuple, Scalar, datum=radius) n = _UNDERSCORE_ + name r = Radius_(radius, Error=TypeError) E = Ellipsoid(r, r, name=n) d = Datum(E, transform=Transforms.Identity, name=n) elif raiser and not d.isSpherical: # raiser if no spherical raise _IsnotError(_spherical_, datum=radius) return d
def _trilaterate3d2(c1, r1, c2, r2, c3, r3, eps=EPS, Vector=None, **Vector_kwds): # MCCABE 13 # (INTERNAL) Intersect three spheres or circles, see L{trilaterate3d2} # above, separated to allow callers to embellish any exceptions def _0f3d(F): # map numpy 4-vector to floats and split F0, x, y, z = map(float, F) return F0, Vector3d(x, y, z) def _N3(t01, x, z): # compute x, y and z and return as Vector v = x.plus(z.times(t01)) n = trilaterate3d2.__name__ return _V_n(v, n, Vector, Vector_kwds) def _real_roots(numpy, *coeffs): # non-complex roots of a polynomial rs = numpy.polynomial.polynomial.polyroots(coeffs) return tuple(float(r) for r in rs if not numpy.iscomplex(r)) def _txt(c1, r1, c2, r2): # check for concentric or too distant spheres d = c1.minus(c2).length if d < abs(r1 - r2): t = _near_concentric_ elif d > (r1 + r2): t = _too_(Fmt.distant(d)) else: return NN return _SPACE_(c1.name, 'and', c2.name, t) np = Vector3d._numpy if np is None: # get numpy, once or ImportError Vector3d._numpy = np = _xnumpy(trilaterate3d2, 1, 10) # macOS' Python 2.7 numpy 1.8 OK c2 = _otherV3d(center2=c2) c3 = _otherV3d(center3=c3) A = [] # 3 x 4 b = [] # 1 x 3 or 3 x 1 for c, d in ((c1, r1), (c2, Radius_(radius2=r2, low=eps)), (c3, Radius_(radius3=r3, low=eps))): A.append((_1_0, -2 * c.x, -2 * c.y, -2 * c.z)) b.append(d**2 - c.length2) try: # <https://NumPy.org/doc/stable/reference/generated/numpy.seterr.html> e = np.seterr(all='raise') # throw FloatingPointError for numpy errors X = np.dot(np.linalg.pinv(A), b) # Moore-Penrose pseudo-inverse Z, _ = _null_space2(np, A, eps) if Z is None: t = () # coincident, colinear, concentric, etc. else: X0, x = _0f3d(X) Z0, z = _0f3d(Z) # quadratic polynomial coefficients, ordered (^0, ^1, ^2) t = _real_roots(np, x.length2 - X0, # fdot(X, -_1_0, *x.xyz) z.dot(x) * 2 - Z0, # fdot(Z, -_0_5, *x.xyz) * 2 z.length2) # fdot(Z, _0_0, *z.xyz) finally: # restore numpy error handling np.seterr(**e) if not t: # coincident, colinear, too distant, no intersection, etc. raise FloatingPointError(_txt(c1, r1, c2, r2) or _txt(c1, r1, c3, r3) or _txt(c2, r2, c3, r3) or (_colinear_ if _iscolinearWith(c1, c2, c3, eps=eps) else _no_(_intersection_))) elif len(t) < 2: # one intersection t *= 2 v = _N3(t[0], x, z) if abs(t[0] - t[1]) < eps: # abutting t = v, v else: # "lowest" intersection first (to avoid test failures) u = _N3(t[1], x, z) t = (u, v) if u < v else (v, u) return t
def _trilaterate(point1, distance1, point2, distance2, point3, distance3, radius=R_M, height=None, useZ=False, **LatLon_LatLon_kwds): # (INTERNAL) Locate a point at given distances from # three other points, see LatLon.triangulate above def _nd2(p, d, r, _i_, *qs): # .toNvector and angular distance squared for q in qs: if p.isequalTo(q, EPS): raise _ValueError(points=p, txt=_coincident_) return p.toNvector(), (Scalar(d, name=_distance_ + _i_) / r)**2 r = Radius_(radius) n1, r12 = _nd2(point1, distance1, r, _1_) n2, r22 = _nd2(point2, distance2, r, _2_, point1) n3, r32 = _nd2(point3, distance3, r, _3_, 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) z = None 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_(r12, -r22, d**2) / (2 * d) # n1->intersection x- and ... y = fsum_(r12, -r32, i**2, j**2, -2 * x * i) / ( 2 * j) # ... y-component # courtesy AleixDev <https://GitHub.com/mrJean1/PyGeodesy/issues/43> z = fsum_(max(r12, r22, r32), -(x**2), -(y**2)) # XXX not just r12! if z > EPS: n = n1.plus(X.times(x)).plus(Y.times(y)) if useZ: # include Z component 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(height) kwds = _xkwds(LatLon_LatLon_kwds, height=h) return n.toLatLon(** kwds) # Nvector(n.x, n.y, n.z).toLatLon(...) # no intersection, d < EPS_2 or abs(j) < EPS_2 or z < EPS t = NN(_no_, _intersection_, _SPACE_) raise IntersectionError(point1=point1, distance1=distance1, point2=point2, distance2=distance2, point3=point3, distance3=distance3, txt=unstr(t, z=z, useZ=useZ))
def _angular(distance, radius): # PYCHOK for export '''(INTERNAL) Return the angular distance in radians. @raise ValueError: Invalid B{C{distance}} or B{C{radius}}. ''' return Distance(distance) / Radius_(radius)
def _intersections2(center1, radius1, center2, radius2, height=None, wrap=True, equidistant=None, tol=_TOL_M, LatLon=None, **LatLon_kwds): '''Iteratively compute the intersection points of two circles each defined by an (ellipsoidal) center point and a radius. @arg center1: Center of the first circle (ellipsoidal C{LatLon}). @arg radius1: Radius of the first circle (C{meter}). @arg center2: Center of the second circle (ellipsoidal C{LatLon}). @arg radius2: Radius of the second circle (C{meter}). @kwarg height: Optional height for the intersection points, overriding the "radical height" at the "radical line" between both centers (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @kwarg equidistant: An azimuthal equidistant projection class (L{Equidistant} or L{EquidistantKarney}) or C{None} for function L{azimuthal.equidistant}. @kwarg tol: Convergence tolerance (C{meter}). @kwarg LatLon: Optional class to return the intersection points (ellipsoidal C{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{LatLon4Tuple}C{(lat, lon, height, datum)} if B{C{LatLon}} is C{None}. For abutting circles, the intersection points are the same instance. @raise ImportError: If B{C{equidistant}} is L{EquidistantKarney}) and package U{geographiclib <https://PyPI.org/project/geographiclib>} not installed or not found. @raise IntersectionError: Concentric, antipodal, invalid or non-intersecting circles or no convergence for B{C{tol}}. @raise TypeError: If B{C{center1}} or B{C{center2}} not ellipsoidal. @raise UnitError: Invalid B{C{radius1}}, B{C{radius2}} or B{C{height}}. @see: U{The B{ellipsoidal} case<https://GIS.StackExchange.com/questions/48937/ calculating-intersection-of-two-circles>}, U{Karney's paper <https://ArXiv.org/pdf/1102.1215.pdf>}, pp 20-21, section 14 I{Maritime Boundaries}, U{circle-circle<https://MathWorld.Wolfram.com/Circle-CircleIntersection.html>} and U{sphere-sphere<https://MathWorld.Wolfram.com/Sphere-SphereIntersection.html>} intersections. ''' c1 = _xellipsoidal(center1=center1) c2 = c1.others(center2, name=_center2_) r1 = Radius_(radius1, name=_radius1_) r2 = Radius_(radius2, name=_radius2_) try: return _intersects2(c1, r1, c2, r2, height=height, wrap=wrap, equidistant=equidistant, tol=tol, LatLon=LatLon, **LatLon_kwds) except (TypeError, ValueError) as x: raise IntersectionError(center1=center1, radius1=radius1, center2=center2, radius2=radius2, txt=str(x))
def intersections2(lat1, lon1, radius1, lat2, lon2, radius2, datum=None, wrap=True): '''Conveniently compute the intersections of two circles each defined by (geodetic/-centric) center point and a radius, using either ... 1) L{vector3d.intersections2} for small distances or if no B{C{datum}} is specified, or ... 2) L{sphericalTrigonometry.intersections2} for a spherical B{C{datum}} or if B{C{datum}} is a C{scalar} representing the earth radius, or ... 3) L{ellipsoidalKarney.intersections2} for an ellipsoidal B{C{datum}} and if I{Karney}'s U{geographiclib<https://PyPI.org/project/geographiclib/>} is installed, or ... 4) L{ellipsoidalVincenty.intersections2} if B{C{datum}} is ellipsoidal otherwise. @arg lat1: Latitude of the first circle center (C{degrees}). @arg lon1: Longitude of the first circle center (C{degrees}). @arg radius1: Radius of the first circle (C{meter}, conventionally). @arg lat2: Latitude of the second circle center (C{degrees}). @arg lon2: Longitude of the second circle center (C{degrees}). @arg radius2: Radius of the second circle (C{meter}, same units as B{C{radius1}}). @kwarg datum: Optional ellipsoidal or spherical datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple} or C{scalar} earth radius in C{meter}, same units as B{C{radius1}} and B{C{radius2}}) or C{None}. @kwarg wrap: Wrap and unroll longitudes (C{bool}). @return: 2-Tuple of the intersection points, each a L{LatLon2Tuple}C{(lat, lon)}. For abutting circles, the intersection points are the same instance. @raise IntersectionError: Concentric, antipodal, invalid or non-intersecting circles or no convergence. @raise TypeError: Invalid B{C{datum}}. @raise UnitError: Invalid B{C{lat1}}, B{C{lon1}}, B{C{radius1}} B{C{lat2}}, B{C{lon2}} or B{C{radius2}}. ''' if datum is None or euclidean(lat1, lon1, lat1, lon2, radius=R_M, adjust=True, wrap=wrap) < _D_I2_: import pygeodesy.vector3d as m def _V2T(x, y, _, **unused): # _ == z unused return _xnamed(LatLon2Tuple(y, x), intersections2.__name__) r1 = m2degrees(Radius_(radius1=radius1), radius=R_M, lat=lat1) r2 = m2degrees(Radius_(radius2=radius2), radius=R_M, lat=lat2) _, lon2 = unroll180(lon1, lon2, wrap=wrap) t = m.intersections2(m.Vector3d(lon1, lat1, 0), r1, m.Vector3d(lon2, lat2, 0), r2, sphere=False, Vector=_V2T) else: def _LL2T(lat, lon, **unused): return _xnamed(LatLon2Tuple(lat, lon), intersections2.__name__) d = _spherical_datum(datum, name=intersections2.__name__) if d.isSpherical: import pygeodesy.sphericalTrigonometry as m elif d.isEllipsoidal: try: if d.ellipsoid.geodesic: pass import pygeodesy.ellipsoidalKarney as m except ImportError: import pygeodesy.ellipsoidalVincenty as m else: raise _AssertionError(datum=d) t = m.intersections2(m.LatLon(lat1, lon1, datum=d), radius1, m.LatLon(lat2, lon2, datum=d), radius2, wrap=wrap, LatLon=_LL2T, height=0) return t