def __init__(self, points, tolerance, radius, shortest, indices, **options): '''New C{Simplify} state. ''' n, self.pts = len2(points) if n > 0: self.n = n self.r = {0: True, n - 1: True} # dict to avoid duplicates if isNumpy2(points) or isTuple2(points): # NOT self.pts self.subset = points.subset if indices: self.indices = True if radius: self.radius = float(radius) if self.radius < self.eps: raise _ValueError(radius=radius, txt=_too_(_small_)) if options: self.options = options # tolerance converted to degrees squared self.s2 = degrees(tolerance / self.radius)**2 if min(self.s2, tolerance) < self.eps: raise _ValueError(tolerance=tolerance, txt=_too_(_small_)) self.s2e = self.s2 + 1 # sentinel # compute either the shortest or perpendicular distance self.d2i = self.d2iS if shortest else self.d2iP # PYCHOK false
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 intersections2(self, rad1, other, rad2, radius=R_M): '''Compute the intersection points of two circles each defined by a center point and a radius. @arg rad1: Radius of the this circle (C{meter} or C{radians}, see B{C{radius}}). @arg other: Center of the other circle (C{Cartesian}). @arg rad2: Radius of the other 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}). @return: 2-Tuple of the intersection points, each C{Cartesian}. The intersection points are the same C{Cartesian} instance for abutting circles. @raise IntersectionError: Concentric, antipodal, invalid or non-intersecting circles. @raise TypeError: If B{C{other}} is not C{Cartesian}. @raise ValueError: Invalid B{C{rad1}}, B{C{rad2}} or B{C{radius}}. @see: U{Java code<https://GIS.StackExchange.com/questions/48937/ calculating-intersection-of-two-circles>} and function L{trilaterate3d2}. ''' x1, x2 = self, self.others(other) r1, r2, x = _rads3(rad1, rad2, radius) if x: x1, x2 = x2, x1 n, q = x1.cross(x2), x1.dot(x2) n2, q21 = n.length2, _1_0 - q**2 if min(abs(q21), n2) < EPS: raise IntersectionError(center=self, other=other, txt=_near_concentric_) try: cr1, cr2 = cos(r1), cos(r2) a = (cr1 - q * cr2) / q21 b = (cr2 - q * cr1) / q21 x0 = x1.times(a).plus(x2.times(b)) except ValueError: raise IntersectionError(center=self, rad1=rad1, other=other, rad2=rad2) x = _1_0 - x0.length2 # XXX x0.dot(x0) if x < EPS: raise IntersectionError(center=self, rad1=rad1, other=other, rad2=rad2, txt=_too_(_distant_)) n = n.times(sqrt(x / n2)) if n.length > EPS: x1, x2 = x0.plus(n), x0.minus(n) else: # abutting circles x1 = x2 = x0 for x in (x1, x2): x.datum = self.datum x.name = self.intersections2.__name__ return x1, x2
def _intersects2(center1, r1, center2, r2, sphere=True, too_d=None, # in .ellipsoidalBase._intersections2 Vector=None, **Vector_kwds): # (INTERNAL) Intersect two spheres or circles, see L{intersections2} # above, separated to allow callers to embellish any exceptions def _V3(x, y, z): v = Vector3d(x, y, z) n = intersections2.__name__ return _V_n(v, n, Vector, Vector_kwds) def _xV3(c1, u, x, y): xy1 = x, y, _1_0 # transform to original space return _V3(fdot(xy1, u.x, -u.y, c1.x), fdot(xy1, u.y, u.x, c1.y), _0_0) c1 = _otherV3d(sphere=sphere, center1=center1) c2 = _otherV3d(sphere=sphere, center2=center2) if r1 < r2: # r1, r2 == R, r c1, c2 = c2, c1 r1, r2 = r2, r1 m = c2.minus(c1) d = m.length if d < max(r2 - r1, EPS): raise ValueError(_near_concentric_) o = fsum_(-d, r1, r2) # overlap == -(d - (r1 + r2)) # compute intersections with c1 at (0, 0) and c2 at (d, 0), like # <https://MathWorld.Wolfram.com/Circle-CircleIntersection.html> if o > EPS: # overlapping, r1, r2 == R, r x = _radical2(d, r1, r2).xline y = _1_0 - (x / r1)**2 if y > EPS: y = r1 * sqrt(y) # y == a / 2 elif y < 0: raise ValueError(_invalid_) else: # abutting y = _0_0 elif o < 0: t = d if too_d is None else too_d raise ValueError(_too_(Fmt.distant(t))) else: # abutting x, y = r1, _0_0 u = m.unit() if sphere: # sphere center and radius c = c1 if x < EPS else ( c2 if x > EPS1 else c1.plus(u.times(x))) t = _V3(c.x, c.y, c.z), Radius(y) elif y > 0: # intersecting circles t = _xV3(c1, u, x, y), _xV3(c1, u, x, -y) else: # abutting circles t = _xV3(c1, u, x, 0) t = t, t return t
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)
def _pts2(points, closed, inull): '''(INTERNAL) Get the points to clip. ''' if closed and inull: n, pts = len2(points) # only remove the final, closing point if n > 1 and _eq(pts[n - 1], pts[0]): n -= 1 pts = pts[:n] if n < 2: raise PointsError(points=n, txt=_too_(_few_)) else: n, pts = points2(points, closed=closed) return n, list(pts)
def _h_x2(xs): '''(INTERNAL) Helper for L{hypot_} and L{hypot2_}. ''' if xs: n, xs = len2(xs) if n > 0: h = float(max(abs(x) for x in xs)) if h > 0: if n > 1: X = Fsum(_1_0) X.fadd((x / h)**2 for x in xs) x2 = X.fsum_(-_1_0) else: x2 = _1_0 else: h = x2 = _0_0 return h, x2 raise _ValueError(xs=xs, txt=_too_(_few_))
def points2(points, closed=True, base=None, Error=PointsError): '''Check a path or polygon represented by points. @arg points: The path or polygon points (C{LatLon}[]) @kwarg closed: Optionally, consider the polygon closed, ignoring any duplicate or closing final B{C{points}} (C{bool}). @kwarg base: Optionally, check all B{C{points}} against this base class, if C{None} don't check. @kwarg Error: Exception to raise (C{ValueError}). @return: A L{Points2Tuple}C{(number, points)} with the number of points and the points C{list} or C{tuple}. @raise PointsError: Insufficient number of B{C{points}}. @raise TypeError: Some B{C{points}} are not B{C{base}} compatible. ''' n, points = len2(points) if closed: # remove duplicate or closing final points while n > 1 and points[n-1] in (points[0], points[n-2]): n -= 1 # XXX following line is unneeded if points # are always indexed as ... i in range(n) points = points[:n] # XXX numpy.array slice is a view! if n < (3 if closed else 1): raise Error(points=n, txt=_too_(_few_)) if base and not (isNumpy2(points) or isTuple2(points)): for i in range(n): base.others(points[i], name=Fmt.SQUARE(points=i)) return Points2Tuple(n, points)
def _intersects2( c1, r1, c2, r2, height=None, wrap=True, # MCCABE 17 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.sphericalTrigonometry import _intersects2 as _si2, LatLon as _LLS from pygeodesy.vector3d import _intersects2 as _vi2 def _latlon4(t, h, n): r = _LatLon4Tuple(t.lat, t.lon, h, t.datum, LatLon, LatLon_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 > (min(E.b, E.a) * PI): raise ValueError(_exceed_PI_radians_) if wrap: # unroll180 == .karney._unroll2 c2 = _unrollon(c1, c2) # 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_(Fmt.distant(m))) f = _radical2(m, r1, r2).ratio # "radical fraction" r = E.rocMean(favg(c1.lat, c2.lat, f=f)) e = max(m2degrees(tol, radius=r), EPS) # get the azimuthal equidistant projection A = _Equidistant2(equidistant, 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 under # method LatLonEllipsoidalBase.intersections2 above ts, ta = [], None for t in ((t1, ) if t1 is t2 else (t1, t2)): p = None # force first d == p to False for i in range(1, _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) d1 = euclid(t1.lat - t.lat, t1.lon - t.lon) if v1 is v2: # abutting t, d = t1, d1 else: t2 = A.reverse(v2.x, v2.y) d2 = euclid(t2.lat - t.lat, t2.lon - t.lon) # consider only the closer intersection t, d = (t1, d1) if d1 < d2 else (t2, d2) # break if below tolerance or if unchanged if d < e or d == p: t._iteration = i # _NamedTuple._iteration ts.append(t) if v1 is v2: # abutting ta = t break p = d else: raise ValueError(_no_(Fmt.convergence(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 clip2(self, points, closed, inull): # MCCABE 17, clip points np, pts = _pts2(points, closed, inull) pcs = _List(inull) # clipped points ne = 0 # number of non-null clip edges no = ni = True # all out- and inside? for e in self.clipedges(): ne += 1 # non-null clip edge # clip points, closed always d2, p2 = self.dot2(pts[np - 1]) for i in range(np): d1, p1 = d2, p2 d2, p2 = self.dot2(pts[i]) if d1 < 0: # p1 inside, ... # pcs.append(p1) if d2 < 0: # ... p2 inside p = p2 else: # ... p2 outside p = self.intersect(p1, p2, e) if d2 > 0: no = False pcs.append(p) elif d2 < 0: # p1 out-, p2 inside p = self.intersect(p1, p2, e) pcs.append(p) pcs.append(p2) if d1 > 0: no = ni = False elif d1 > 0: # both out ni = False if pcs: # replace points pts[:] = pcs pcs[:] = [] np = len(pts) if ne < 3: raise ClipError(self.name, ne, self._cs, txt=_too_(_few_)) # ni is True iff all points are on or on the # right side (i.e. inside) of all clip edges, # no is True iff all points are on or at one # side (left or right) of each clip edge: if # none are inside (ni is False) and if all # are on the same side (no is True), then all # must be outside if no and not ni: np, pts = 0, [] elif np > 1: p = pts[0] if closed: # close clipped pts if _neq(pts[np - 1], p): pts.append(p) np += 1 elif not inull: # open clipped pts while np > 0 and _eq(pts[np - 1], p): pts.pop() np -= 1 # assert len(pts) == np return np, pts