def _unite_segments_cross(first: Segment, second: Segment, context: Context) -> Multisegment: cross_point = context.segments_intersection(first, second) segment_cls = context.segment_cls return context.multisegment_cls([segment_cls(first.start, cross_point), segment_cls(second.start, cross_point), segment_cls(cross_point, first.end), segment_cls(cross_point, second.end)])
def test_step(context: Context, segments: List[Segment]) -> None: *rest_segments, last_segment = segments result = segments_intersections(rest_segments) next_result = segments_intersections(segments) assert (next_result.keys() == ( result.keys() | {(index, len(segments) - 1) for index, segment in enumerate(rest_segments) if context. segments_relation(segment, last_segment) is not Relation.DISJOINT})) assert result.items() <= next_result.items() assert all(segment_id < next_segment_id == len(segments) - 1 for segment_id, next_segment_id in (next_result.keys() - result.keys())) assert all( context.segments_intersection( segments[segment_id], segments[next_segment_id]) == next_result[( segment_id, next_segment_id)][0] if len(next_result[( segment_id, next_segment_id)]) == 1 else context.segments_intersection( segments[segment_id], segments[next_segment_id]) not in (Relation.DISJOINT, Relation.TOUCH, Relation.CROSS) and ( to_sorted_pair(*next_result[(segment_id, next_segment_id)]) == next_result[(segment_id, next_segment_id)]) and all( context.segment_contains_point(segments[segment_id], point) for point in next_result[(segment_id, next_segment_id)]) and all( context.segment_contains_point(segments[next_segment_id], point) for point in next_result[(segment_id, next_segment_id)]) for segment_id, next_segment_id in (next_result.keys() - result.keys())) assert all( context.segments_relation(segments[segment_id], segments[next_segment_id]) is not Relation.DISJOINT for segment_id, next_segment_id in (next_result.keys() - result.keys()))
def intersect_segments(first: Segment, second: Segment, context: Context) -> Union_[Empty, Multipoint, Segment]: relation = context.segments_relation(first, second) if relation is Relation.DISJOINT: return context.empty elif relation is Relation.TOUCH or relation is Relation.CROSS: return context.multipoint_cls([context.segments_intersection(first, second)]) else: return (first if relation is Relation.EQUAL or relation is Relation.COMPONENT else (second if relation is Relation.COMPOSITE else _intersect_segments_overlap(first, second, context)))
def _subtract_segment_from_multisegment(multisegment: Multisegment, segment: Segment, context: Context) -> List[Segment]: result = [] segment_cls = context.segment_cls for index, sub_segment in enumerate(multisegment.segments): relation = context.segments_relation(segment, sub_segment) if relation is Relation.EQUAL: result.extend(multisegment.segments[index + 1:]) break elif relation is Relation.COMPONENT: left_start, left_end, right_start, right_end = sorted([ sub_segment.start, sub_segment.end, segment.start, segment.end]) result.extend( [segment_cls(right_start, right_end)] if left_start == segment.start or left_start == segment.end else ((([segment_cls(left_start, left_end)] if right_start == right_end else [segment_cls(left_start, left_end), segment_cls(right_start, right_end)]) if (right_start == segment.start or right_start == segment.end) else [segment_cls(left_start, left_end)]) if left_end == segment.start or left_end == segment.end else [segment_cls(left_start, right_start)])) result.extend(multisegment.segments[index + 1:]) break elif relation is Relation.CROSS: cross_point = context.segments_intersection(sub_segment, segment) result.append(segment_cls(sub_segment.start, cross_point)) result.append(segment_cls(cross_point, sub_segment.end)) elif relation is Relation.OVERLAP: result.append(_subtract_segments_overlap(sub_segment, segment, context)) elif relation is not Relation.COMPOSITE: result.append(sub_segment) return result
def relate_segment(multisegment: Multisegment, segment: Segment, context: Context) -> Relation: is_segment_superset = has_no_touch = has_no_cross = has_no_overlap = True # orientations of multisegment's segments # which touch given segment in the middle middle_touching_orientations = {} # type: Dict[Point, Orientation] components = [] start, end = segment_endpoints = segment.start, segment.end if start > end: start, end = end, start for index, sub_segment in enumerate(multisegment.segments): sub_segment_endpoints = sub_segment.start, sub_segment.end relation = relate_segments(sub_segment, segment, context) if relation is Relation.COMPONENT or relation is Relation.EQUAL: return Relation.COMPONENT elif relation is Relation.COMPOSITE: if has_no_overlap: has_no_overlap = False if start in sub_segment_endpoints: start = max(sub_segment_endpoints) segment_endpoints = start, end elif end in sub_segment_endpoints: end = min(sub_segment_endpoints) segment_endpoints = start, end else: components.append(to_sorted_pair(sub_segment_endpoints)) elif relation is Relation.OVERLAP: if is_segment_superset: is_segment_superset = False if has_no_overlap: has_no_overlap = False start, end = segment_endpoints = _subtract_segments_overlap( segment_endpoints, sub_segment_endpoints) else: if is_segment_superset: is_segment_superset = False if has_no_overlap: if relation is Relation.TOUCH: if has_no_touch: has_no_touch = False if has_no_cross: intersection = context.segments_intersection( sub_segment, segment) if intersection != start and intersection != end: sub_start, sub_end = sub_segment_endpoints non_touched_endpoint = (sub_start if intersection == sub_end else sub_end) try: previous_orientation = ( middle_touching_orientations[intersection]) except KeyError: middle_touching_orientations[intersection] = ( context.angle_orientation( start, end, non_touched_endpoint)) else: if (context.angle_orientation( start, end, non_touched_endpoint) is not previous_orientation): has_no_cross = False elif has_no_cross and relation is Relation.CROSS: has_no_cross = False if has_no_overlap: return (Relation.DISJOINT if has_no_touch and has_no_cross else (Relation.TOUCH if has_no_cross else Relation.CROSS)) elif components: components_iterator = iter(components) min_component_start, max_component_end = next(components_iterator) components_starts = {min_component_start} for component_start, component_end in components_iterator: components_starts.add(component_start) if min_component_start > component_start: min_component_start = component_start if max_component_end < component_end: max_component_end = component_end return ( (Relation.EQUAL if is_segment_superset else Relation.COMPONENT) if (min_component_start == start and max_component_end == end and all(component_end in components_starts or component_end == max_component_end for _, component_end in components)) else (Relation.COMPOSITE if is_segment_superset else Relation.OVERLAP)) else: return ( (Relation.EQUAL if is_segment_superset else Relation.COMPONENT) if start == end else (Relation.COMPOSITE if is_segment_superset else Relation.OVERLAP))
def segments_intersection(context: Context, first_start, first_end, second_start, second_end): return context.segments_intersection(first_start, first_end, second_start, second_end)