def _relate_segment_to_contour(contour: Contour, segment: Segment, context: Context) -> Relation: # similar to segment-in-contour check # but cross has higher priority over overlap # because cross with contour will be considered as cross with region # whereas overlap with contour can't be an overlap with region # and should be classified by further analysis has_no_touch = has_no_overlap = True last_touched_edge_index = last_touched_edge_start = None start, end = segment.start, segment.end for index, edge in enumerate(context.contour_segments(contour)): edge_start, edge_end = edge_endpoints = edge.start, edge.end relation_with_edge = relate_segments(edge, segment, context) if (relation_with_edge is Relation.COMPONENT or relation_with_edge is Relation.EQUAL): return Relation.COMPONENT elif (relation_with_edge is Relation.OVERLAP or relation_with_edge is Relation.COMPOSITE): if has_no_overlap: has_no_overlap = False elif relation_with_edge is Relation.TOUCH: if has_no_touch: has_no_touch = False elif (index - last_touched_edge_index == 1 and start not in edge_endpoints and end not in edge_endpoints and (context.angle_orientation( start, end, edge_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, last_touched_edge_start, edge_start, edge_end, context)): return Relation.CROSS last_touched_edge_index = index last_touched_edge_start = edge_start elif relation_with_edge is Relation.CROSS: return Relation.CROSS vertices = contour.vertices if not has_no_touch and last_touched_edge_index == len(vertices) - 1: first_edge_endpoints = first_edge_start, first_edge_end = ( vertices[-1], vertices[0]) if (relate_segments( context.segment_cls(first_edge_start, first_edge_end), segment, context) is Relation.TOUCH and start not in first_edge_endpoints and end not in first_edge_endpoints and (context.angle_orientation(start, end, first_edge_start) is Orientation.COLLINEAR) and point_vertex_line_divides_angle( start, vertices[-2], first_edge_start, first_edge_end, context)): return Relation.CROSS return ((Relation.DISJOINT if has_no_touch else Relation.TOUCH) if has_no_overlap else Relation.OVERLAP)
def to_contour_vertices_orientation(vertices: Sequence[Point], context: Context) -> Orientation: if len(vertices) < 3: return Orientation.COLLINEAR index = min(range(len(vertices)), key=vertices.__getitem__) return context.angle_orientation(vertices[index - 1], vertices[index], vertices[(index + 1) % len(vertices)])
def _locate_point(region: Region, point: Point, context: Context) -> Tuple[Optional[int], Location]: result = False point_y = point.y for index, edge in enumerate(context.contour_segments(region)): if locate_point_in_segment(edge, point, context) is Location.BOUNDARY: return index, Location.BOUNDARY start, end = edge.start, edge.end if ((start.y > point_y) is not (end.y > point_y) and ((end.y > start.y) is (context.angle_orientation( start, end, point) is Orientation.COUNTERCLOCKWISE))): result = not result return None, (Location.INTERIOR if result else Location.EXTERIOR)
def _unite_segments_touch(first: Segment, second: Segment, context: Context) -> Union_[Multisegment, Segment]: return (context.multisegment_cls([first, second]) if ((first.start != second.start or (context.angle_orientation(first.start, first.end, second.end) is not Orientation.COLLINEAR)) and (first.start != second.end or (context.angle_orientation(first.start, first.end, second.start) is not Orientation.COLLINEAR)) and (first.end != second.start or (context.angle_orientation(first.end, first.start, second.end) is not Orientation.COLLINEAR)) and (first.end != second.end or (context.angle_orientation(first.end, first.start, second.start) is not Orientation.COLLINEAR))) else context.segment_cls(min(first.start, first.end, second.start, second.end), max(first.start, first.end, second.start, second.end)))
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 to_contour_orientation(contour: Contour, context: Context) -> Orientation: vertices = contour.vertices index = min(range(len(vertices)), key=vertices.__getitem__) return context.angle_orientation(vertices[index - 1], vertices[index], vertices[(index + 1) % len(vertices)])
def point_vertex_line_divides_angle(point: Point, first_ray_point: Point, vertex: Point, second_ray_point: Point, context: Context) -> bool: return (context.angle_orientation(vertex, first_ray_point, point) is context.angle_orientation(vertex, point, second_ray_point))