def _null_space2(numpy, A, eps): # (INTERNAL) Return the nullspace and rank of matrix A # @see: <https://SciPy-Cookbook.ReadTheDocs.io/items/RankNullspace.html>, # <https://NumPy.org/doc/stable/reference/generated/numpy.linalg.svd.html>, # <https://StackOverflow.com/questions/19820921>, # <https://StackOverflow.com/questions/2992947> and # <https://StackOverflow.com/questions/5889142> A = numpy.array(A) m = max(numpy.shape(A)) if m != 4: # for this usage raise _AssertionError(shape=m, txt=modulename(_null_space2, True)) # if needed, square A, pad with zeros A = numpy.resize(A, m * m).reshape(m, m) # try: # no numpy.linalg.null_space <https://docs.SciPy.org/doc/> # return scipy.linalg.null_space(A) # XXX no scipy.linalg? # except AttributeError: # pass _, s, v = numpy.linalg.svd(A) t = max(eps, eps * s[0]) # tol, s[0] is largest singular r = numpy.sum(s > t) # rank if r == 3: # get null_space n = numpy.transpose(v[r:]) s = numpy.shape(n) if s != (m, 1): # bad null_space shape raise _AssertionError(shape=s, txt=modulename(_null_space2, True)) e = float(numpy.max(numpy.abs(numpy.dot(A, n)))) if e > t: # residual not near-zero raise _AssertionError(eps=e, txt=modulename(_null_space2, True)) else: # coincident, colinear, concentric centers, ambiguous, etc. n = None # del A, s, vh # release numpy return n, r
def _update(self, updated, *attrs): '''(INTERNAL) Zap cached instance attributes. ''' if updated and attrs: for a in attrs: # zap attrs to None if getattr(self, a, None) is not None: setattr(self, a, None) elif not hasattr(self, a): raise _AssertionError('.%s invalid: %r' % (a, self))
def _xother3(inst, other, name=_other_, up=1, **name_other): '''(INTERNAL) Get C{name} and C{up} for a named C{other}. ''' if name_other: # and not other and len(name_other) == 1 name, other = _xkwds_popitem(name_other) elif other and len(other) == 1: other = other[0] else: raise _AssertionError(name, other, txt=classname(inst, prefixed=True)) return other, name, up
def notOverloaded(inst, name, *args, **kwds): # PYCHOK no cover '''Raise an C{AssertionError} for a method or property not overloaded. @arg inst: Instance (C{any}). @arg name: Method, property or name (C{str} or C{callable}). @arg args: Method or property positional arguments (any C{type}s). @arg kwds: Method or property keyword arguments (any C{type}s). ''' t = _notError(inst, name, args, kwds) raise _AssertionError(t, txt=notOverloaded.__name__)
def _update(self, updated, *attrs): '''(INTERNAL) Zap cached instance attributes. ''' if updated and attrs: for a in attrs: # zap attrs to None if getattr(self, a, None) is not None: setattr(self, a, None) elif not hasattr(self, a): a = NN(_DOT_, a, _SPACE_, _invalid_) raise _AssertionError(a, txt=repr(self))
def unregister(self): '''Remove this instance from its C{_NamedEnum} registry. @raise _AssertionError: Mismatch of this and registered item. @raise NameError: This item is unregistered. ''' enum = self._enum if enum and self.name and self.name in enum: item = enum.unregister(self.name) if item is not self: raise _AssertionError('%r vs %r' % (item, self))
def clipCS3(points, lowerleft, upperright, closed=False, inull=False): '''Clip a path against a rectangular clip box using the U{Cohen-Sutherland <https://WikiPedia.org/wiki/Cohen-Sutherland_algorithm>} algorithm. @arg points: The points (C{LatLon}[]). @arg lowerleft: Bottom-left corner of the clip box (C{LatLon}). @arg upperright: Top-right corner of the clip box (C{LatLon}). @kwarg closed: Optionally, close the path (C{bool}). @kwarg inull: Optionally, include null edges if inside (C{bool}). @return: Yield a L{ClipCS3Tuple}C{(start, end, index)} for each edge of the clipped path. @raise ClipError: The B{C{lowerleft}} and B{C{upperright}} corners specify an invalid clip box. @raise PointsError: Insufficient number of B{C{points}}. ''' cs = _CS(lowerleft, upperright, name=clipCS3.__name__) n, pts = _points2(points, closed, inull) i, m = _imdex2(closed, n) cmbp = cs.code4(pts[i]) for i in range(m, n): c1, m1, b1, p1 = cmbp c2, m2, b2, p2 = cmbp = cs.code4(pts[i]) if c1 & c2: # edge outside continue if not cs.edge(p1, p2): if inull: # null edge if not c1: yield ClipCS3Tuple(p1, p1, i) elif not c2: yield ClipCS3Tuple(p2, p2, i) continue for _ in range(5): if c1: # clip p1 c1, m1, b1, p1 = m1(b1, p1) elif c2: # clip p2 c2, m2, b2, p2 = m2(b2, p2) else: # inside if inull or _neq(p1, p2): yield ClipCS3Tuple(p1, p2, i) break if c1 & c2: # edge outside break else: # PYCHOK no cover raise _AssertionError(_dot_(cs.name, 'for_else'))
def unregister(self): '''Remove this instance from its C{_NamedEnum} registry. @raise AssertionError: Mismatch of this and registered item. @raise NameError: This item is unregistered. ''' enum = self._enum if enum and self.name and self.name in enum: item = enum.unregister(self.name) if item is not self: t = _SPACE_(repr(item), _vs_, repr(self)) raise _AssertionError(t)
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 or on top of 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: # PYCHOK no cover raise _AssertionError(_dot_(self.name, self.intersect.__name__)) r = fsum_(self._xy, -p1.lat * self._dx, p1.lon * self._dy) / fp y = p1.lat + r * fy x = p1.lon + r * fx return _LLi_(y, x, p1.classof, edge)
def _allis2(llis, m=1, Error=HeightError): # imported by .geoids # dtermine return type and convert lli C{LatLon}s to list if not isinstance(llis, tuple): # llis are *args raise _AssertionError('type(%s): %r' % ('*llis', llis)) n = len(llis) if n == 1: # convert single lli to 1-item list llis = llis[0] try: n, llis = len2(llis) _as = _alist # return list of interpolated heights except TypeError: # single lli n, llis = 1, [llis] _as = _ascalar # return single interpolated heights else: # of 0, 2 or more llis _as = _atuple # return tuple of interpolated heights if n < m: raise _insufficientError(m, Error=Error, llis=n) return _as, llis
def _ascalar(ais): # imported by .geoids # return single float, not numpy.float64 ais = list(ais) # np.array, etc. to list if len(ais) != 1: raise _AssertionError('%s(%r): %s != 1' % (_len_, ais, len(ais))) return float(ais[0]) # remove np.<type>
points2 as _points2, _scale_rad, thomas_, vincentys_ from pygeodesy.interns import _datum_, _degrees_, _distanceTo_, _dot_, _item_sq, \ _meter_, NN, _points_, _radians_, _radians2_, _units_ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _FOR_DOCS from pygeodesy.named import LatLon2Tuple, _Named, _NamedTuple, \ notOverloaded, PhiLam2Tuple from pygeodesy.utily import unrollPI from collections import defaultdict from math import radians __all__ = _ALL_LAZY.frechet __version__ = '20.07.08' if not 0 < EPS < EPS1 < 1: raise _AssertionError('%s < %s: 0 < %.6g < %.6g < 1' % ('EPS', 'EPS1', EPS, EPS1)) def _fraction(fraction, n): f = 1 # int, no fractional indices if fraction in (None, 1): pass elif not (isscalar(fraction) and EPS < fraction < EPS1 and (float(n) - fraction) < n): raise FrechetError(fraction=fraction) elif fraction < EPS1: f = float(fraction) return f class FrechetError(PointsError):
def nop4(self, b, p): # PYCHOK no cover if p: # should never get here raise _AssertionError(_dot_(self.name, self.nop4.__name__)) return _CS._IN, self.nop4, b, p
def _intersects2( c1, r1, c2, r2, height=None, wrap=True, # MCCABE 16 equidistant=None, tol=_TOL_M, LatLon=None, **LatLon_kwds): # (INTERNAL) Intersect two spherical circles, see L{_intersections2} # above, separated to allow callers to embellish any exceptions from pygeodesy.formy import _euclidean, _radical2 from pygeodesy.sphericalTrigonometry import _intersects2 as _si2, LatLon as _LLS from pygeodesy.utily import m2degrees, unroll180 from pygeodesy.vector3d import _intersects2 as _vi2 def _latlon4(t, h, n): if LatLon is None: r = LatLon4Tuple(t.lat, t.lon, h, t.datum) else: kwds = _xkwds(LatLon_kwds, datum=t.datum, height=h) r = LatLon(t.lat, t.lon, **kwds) r._iteration = t.iteration # ._iteration for tests return _xnamed(r, n) if r1 < r2: c1, c2 = c2, c1 r1, r2 = r2, r1 E = c1.ellipsoids(c2) if r1 > (E.b * PI): raise ValueError(_exceed_PI_radians_) if wrap: # unroll180 == .karney._unroll2 _, lon2 = unroll180(c1.lon, c2.lon, wrap=True) if lon2 != c2.lon: c2 = c2.classof(c2.lat, lon2, c2.height, datum=c2.datum) # distance between centers and radii are # measured along the ellipsoid's surface m = c1.distanceTo(c2, wrap=False) # meter if m < max(r1 - r2, EPS): raise ValueError(_near_concentric_) if fsum_(r1, r2, -m) < 0: raise ValueError(_too_distant_fmt_ % (m, )) f = _radical2(m, r1, r2).ratio # "radical lat" r = E.rocMean(favg(c1.lat, c2.lat, f=f)) e = max(m2degrees(tol, radius=r), EPS) # get the azimuthal equidistant projection if equidistant is None: from pygeodesy.azimuthal import equidistant as A else: A = equidistant # preferably EquidistantKarney A = A(0, 0, datum=c1.datum) # gu-/estimate initial intersections, spherically ... t1, t2 = _si2(_LLS(c1.lat, c1.lon, height=c1.height), r1, _LLS(c2.lat, c2.lon, height=c2.height), r2, radius=r, height=height, wrap=False, too_d=m) h, n = t1.height, t1.name # ... and then iterate like Karney suggests to find # tri-points of median lines, @see: references above ts, ta = [], None for t in ((t1, ) if t1 is t2 else (t1, t2)): p = None # force d == p False for i in range(_TRIPS): A.reset(t.lat, t.lon) # gu-/estimate as origin # convert centers to projection space t1 = A.forward(c1.lat, c1.lon) t2 = A.forward(c2.lat, c2.lon) # compute intersections in projection space v1, v2 = _vi2( t1, r1, # XXX * t1.scale?, t2, r2, # XXX * t2.scale?, sphere=False, too_d=m) # convert intersections back to geodetic t1 = A.reverse(v1.x, v1.y) t2 = A.reverse(v2.x, v2.y) # consider only the closer intersection d1 = _euclidean(t1.lat - t.lat, t1.lon - t.lon) d2 = _euclidean(t2.lat - t.lat, t2.lon - t.lon) # break if below tolerance or if unchanged t, d = (t1, d1) if d1 < d2 else (t2, d2) if d < e or d == p: t._iteration = i + 1 # _NamedTuple._iteration ts.append(t) if v1 is v2: # abutting ta = t break p = d else: raise ValueError(_no_convergence_fmt_ % (tol, )) if ta: # abutting circles r = _latlon4(ta, h, n) elif len(ts) == 2: return _latlon4(ts[0], h, n), _latlon4(ts[1], h, n) elif len(ts) == 1: # XXX assume abutting r = _latlon4(ts[0], h, n) else: raise _AssertionError(ts=ts) return r, r
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
LatLonSphericalBase, _rads3 from pygeodesy.units import Bearing_, Height, Radius, Radius_, Scalar from pygeodesy.utily import acos1, asin1, degrees90, degrees180, degrees2m, \ iterNumpy2, radiansPI2, sincos2, tan_2, \ unrollPI, wrapPI from pygeodesy.vector3d import sumOf, Vector3d from math import asin, atan2, copysign, cos, degrees, hypot, radians, sin __all__ = _ALL_LAZY.sphericalTrigonometry __version__ = '20.08.14' _EPS_I2 = 4.0 * EPS _PI_EPS_I2 = PI - _EPS_I2 if _PI_EPS_I2 >= PI: raise _AssertionError(_PI_EPS_I2=_PI_EPS_I2) def _destination2(a, b, r, t): '''(INTERNAL) Destination phi- and longitude in C{radians}. @arg a: Latitude (C{radians}). @arg b: Longitude (C{radians}). @arg r: Angular distance (C{radians}). @arg t: Bearing (compass C{radians}). @return: 2-Tuple (phi, lam) of (C{radians}, C{radiansPI}). ''' # see <https://www.EdWilliams.org/avform.htm#LL> sa, ca, sr, cr, st, ct = sincos2(a, r, t)