def test_segment_sort_along(p1, p2, tvals): # Get rid of pathological cases. assume(p1.distance(p2) > 0.001) tvals = [t / 100 for t in tvals] fuzz = [1e-10, -1e-10] points = [along_the_way(p1, p2, t) for t in tvals] points = [ Point(x + f, y + f) for (x, y), f in zip(points, itertools.cycle(fuzz)) ] # Calculate the smallest distance between any pair of points. If we get # the wrong answer from sort_along, then the total distance will be off by # at least twice this. min_gap = min(q1.distance(q2) for q1, q2 in all_pairs(points + [p1, p2])) seg = Segment(p1, p2) spoints = seg.sort_along(points) assert len(spoints) == len(points) assert all(pt in points for pt in spoints) original = Point(*p1).distance(Point(*p2)) total = (Point(*p1).distance(Point(*spoints[0])) + sum( Point(*p).distance(Point(*q)) for p, q in adjacent_pairs(spoints)) + Point(*spoints[-1]).distance(Point(*p2))) # The total distance will be wrong by at least 2*min_gap if it is wrong. assert total - original < 2 * min_gap
def test_segment_touches(p1, p2, p3, p4, isect): seg12 = Segment(p1, p2) seg34 = Segment(p3, p4) if isect is None: assert not seg12.touches(seg34) else: assert seg12.touches(seg34)
def path_pieces(path, segs_to_points): """Produce a new series of paths, split at intersection points. Yields a series of pieces (paths). The pieces trace the same line as the original path. The endpoints of the pieces are all intersection points in `segs_to_points`, or the endpoints of the original path, if it isn't circular. The pieces are in order along `path`, so consecutive pieces end and begin at the same point. If `path` is closed, then the first piece returned will begin at the first cut, not at the path's first point. """ # If path is circular, then the first piece we collect has to be added to # the last piece, so save it for later. collecting_head = path.closed head = None piece = [] for pt in path: if not piece: piece.append(pt) else: seg = Segment(piece[-1], pt) cuts = segs_to_points.get(seg) if cuts is not None: cuts = seg.sort_along(cuts) for cut in cuts: ptcut = Point(*cut) piece.append(ptcut) if collecting_head: head = piece collecting_head = False else: yield Path(piece) piece = [ptcut] piece.append(pt) piece = Path(piece) if head: piece = piece.join(Path(head)) yield piece
def test_segment_intersect_error(p1, p2, p3, p4, err): with pytest.raises(err): assert Segment(p1, p2).intersect(Segment(p3, p4))
def test_segment_intersection(p1, p2, p3, p4, isect): assert Segment(p1, p2).intersect(Segment(p3, p4)) == isect
from hypothesis import assume, given from hypothesis.strategies import builds, lists, integers, tuples from zellij.defuzz import Defuzzer from zellij.euclid import collinear, Segment, BadGeometry from zellij.intersection import segment_intersections from zellij.postulates import all_pairs nums = integers(min_value=-1000, max_value=1000) points = tuples(nums, nums) segments = builds(lambda l: Segment(*l), lists(points, min_size=2, max_size=2, unique=True)) @given(lists(segments, min_size=2, max_size=100, unique=True)) def test_intersections(segments): defuzz = Defuzzer().defuzz # Check that none of our segment pairs are pathological, and collect the # true answers the hard way, by checking pair-wise. true = set() for s1, s2 in all_pairs(segments): try: ipt = s1.intersect(s2) if ipt is not None: true.add(defuzz(ipt)) except BadGeometry: # If two segments don't have an answer, then don't use this test # case. assume(False) # Run the actual function we care about.
def test_segment_touches_errors(p1, p2, p3, p4, err): assert err == CoincidentLines # ick assert Segment(p1, p2).touches(Segment(p3, p4))
def test_segment_intersection(p1, p2, p3, p4, isect): seg12 = Segment(p1, p2) seg34 = Segment(p3, p4) assert seg12.intersect(seg34) == isect