Skip to content

Commit

Permalink
Complete algorithm (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
lycantropos committed Apr 2, 2021
1 parent c163e43 commit d4a1e6a
Show file tree
Hide file tree
Showing 16 changed files with 610 additions and 412 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ True
we can also find in which points segments intersect
```python
>>> from bentley_ottmann.planar import segments_intersections
>>> segments_intersections(unit_segments) == {Point(0, 0): {(0, 1)}}
>>> segments_intersections(unit_segments) == {(0, 1): (Point(0, 0),)}
True

```
Expand Down
102 changes: 102 additions & 0 deletions bentley_ottmann/core/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from typing import (Iterable,
List,
Optional,
Sequence)

from ground.base import (Context,
Orientation,
Relation)
from ground.hints import (Point,
Segment)

from .event import (Event,
LeftEvent)
from .events_queue import EventsQueue
from .sweep_line import SweepLine
from .utils import (classify_overlap,
to_pairs_combinations)


def sweep(segments: Sequence[Segment],
*,
context: Context) -> Iterable[LeftEvent]:
events_queue = EventsQueue.from_segments(segments,
context=context)
sweep_line = SweepLine(context)
start = (events_queue.peek().start
if events_queue
else None) # type: Optional[Point]
same_start_events = [] # type: List[Event]
while events_queue:
event = events_queue.pop()
if event.start == start:
same_start_events.append(event)
else:
complete_events_relations(same_start_events,
context=context)
yield from to_processed_events(same_start_events)
same_start_events, start = [event], event.start
if event.is_left:
equal_segment_event = sweep_line.find_equal(event)
if equal_segment_event is None:
sweep_line.add(event)
below_event = sweep_line.below(event)
if below_event is not None:
events_queue.detect_intersection(below_event, event,
sweep_line)
above_event = sweep_line.above(event)
if above_event is not None:
events_queue.detect_intersection(event, above_event,
sweep_line)
else:
# found equal segments' fragments
equal_segment_event.merge_with(event)
else:
event = event.left
equal_segment_event = sweep_line.find_equal(event)
if equal_segment_event is not None:
above_event, below_event = (
sweep_line.above(equal_segment_event),
sweep_line.below(equal_segment_event))
sweep_line.remove(equal_segment_event)
if below_event is not None and above_event is not None:
events_queue.detect_intersection(below_event, above_event,
sweep_line)
if event is not equal_segment_event:
event.merge_with(equal_segment_event)
complete_events_relations(same_start_events,
context=context)
yield from to_processed_events(same_start_events)


def complete_events_relations(same_start_events: Sequence[Event],
*,
context: Context) -> None:
for first, second in to_pairs_combinations(same_start_events):
first_left, second_left = (first if first.is_left else first.left,
second if second.is_left else second.left)
segments_overlap = (
first.is_left is not second.is_left
and first.original_start != second.original_start
and (context.angle_orientation(first.start, first.end,
second.end)
is Orientation.COLLINEAR))
if segments_overlap:
relation = classify_overlap(first_left.original_start,
first_left.original_end,
second_left.original_start,
second_left.original_end)
else:
intersection_point = first.start
relation = (Relation.TOUCH
if (intersection_point == first.original_start
or intersection_point == second.original_start)
else Relation.CROSS)
first.register_tangent(second)
second.register_tangent(first)
first_left.register_relation(relation)
second_left.register_relation(relation.complement)


def to_processed_events(events: Iterable[Event]) -> List[LeftEvent]:
return [candidate.left for candidate in events if not candidate.is_left]
60 changes: 0 additions & 60 deletions bentley_ottmann/core/bentley_ottmann.py

This file was deleted.

184 changes: 168 additions & 16 deletions bentley_ottmann/core/event.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,189 @@
from abc import (ABC,
abstractmethod)
from reprlib import recursive_repr
from typing import (Dict,
List,
Optional)
Optional,
Sequence,
Set)

from ground.base import Relation
from ground.hints import Point
from ground.hints import (Point,
Segment)
from reprit.base import generate_repr

from .hints import Ids
from .utils import (classify_overlap,
to_sorted_pair)


class Event:
__slots__ = ('complement', 'is_left_endpoint', 'original_start',
'relations', 'segments_ids', 'start')
class Event(ABC):
__slots__ = ()

is_left = False
left = None # type: Optional['LeftEvent']
right = None # type: Optional['RightEvent']

@property
@abstractmethod
def end(self) -> Point:
"""Returns end of the event."""

@property
@abstractmethod
def original_end(self) -> Point:
"""Returns original end of the event."""

@property
@abstractmethod
def original_start(self) -> Point:
"""Returns original start of the segment."""

@abstractmethod
def register_tangent(self, tangent: 'Event') -> None:
"""Registers new tangent to the event"""

@property
@abstractmethod
def segments_ids(self) -> Set[int]:
"""Returns segments ids of the event."""

@property
@abstractmethod
def start(self) -> Point:
"""Returns start of the event."""

@property
@abstractmethod
def tangents(self) -> Sequence['Event']:
"""Returns tangents of the event."""


class LeftEvent(Event):
@classmethod
def from_segment(cls, segment: Segment, segment_id: int) -> 'LeftEvent':
start, end = to_sorted_pair(segment.start, segment.end)
result = LeftEvent(start, None, start, {start: {end: {segment_id}}})
result.right = RightEvent(end, result, end)
return result

is_left = True

@property
def segments_ids(self) -> Set[int]:
return self.parts_ids[self.start][self.end]

@property
def start(self) -> Point:
return self._start

@property
def original_start(self) -> Point:
return self._original_start

@property
def end(self) -> Point:
return self.right.start

@property
def original_end(self) -> Point:
return self.right.original_start

@property
def tangents(self) -> Sequence[Event]:
return self._tangents

__slots__ = ('right', 'parts_ids', '_original_start',
'_start', '_tangents', '_relations_mask')

def __init__(self,
start: Point,
complement: Optional['Event'],
is_left_endpoint: bool,
right: Optional['RightEvent'],
original_start: Point,
segments_ids: Ids,
relations: Dict[Relation, List[Ids]]) -> None:
self.complement, self.original_start, self.start = (
complement, original_start, start)
self.is_left_endpoint = is_left_endpoint
self.relations, self.segments_ids = relations, segments_ids
parts_ids: Dict[Point, Dict[Point, Set[int]]]) -> None:
self.right, self.parts_ids, self._original_start, self._start = (
right, parts_ids, original_start, start)
self._relations_mask = 0
self._tangents = [] # type: List[Event]

__repr__ = recursive_repr()(generate_repr(__init__))

def divide(self, break_point: Point) -> 'LeftEvent':
"""Divides the event at given break point and returns tail."""
segments_ids = self.segments_ids
(self.parts_ids.setdefault(self.start, {})
.setdefault(break_point, set()).update(segments_ids))
(self.parts_ids.setdefault(break_point, {})
.setdefault(self.end, set()).update(segments_ids))
result = self.right.left = LeftEvent(
break_point, self.right, self.original_start,
self.parts_ids)
self.right = RightEvent(break_point, self, self.original_end)
return result

def has_only_relations(self, *relations: Relation) -> bool:
mask = self._relations_mask
for relation in relations:
mask &= ~(1 << relation)
return not mask

def merge_with(self, other: 'LeftEvent') -> None:
assert self.start == other.start and self.end == other.end
full_relation = classify_overlap(
other.original_start, other.original_end, self.original_start,
self.original_end)
self.register_relation(full_relation)
other.register_relation(full_relation.complement)
start, end = self.start, self.end
self.parts_ids[start][end] = other.parts_ids[start][end] = (
self.parts_ids[start][end] | other.parts_ids[start][end])

def register_tangent(self, tangent: Event) -> None:
assert self.start == tangent.start
assert tangent not in self._tangents
self._tangents.append(tangent)

def register_relation(self, relation: Relation) -> None:
self._relations_mask |= 1 << relation


class RightEvent(Event):
@property
def end(self) -> Point:
return self.complement.start
return self.left.start

@property
def original_end(self) -> Point:
return self.complement.original_start
return self.left.original_start

@property
def original_start(self) -> Point:
return self._original_start

@property
def segments_ids(self) -> Set[int]:
return self.left.segments_ids

@property
def start(self) -> Point:
return self._start

@property
def tangents(self) -> Sequence[Event]:
return self._tangents

__slots__ = 'left', '_original_start', '_start', '_tangents'

def __init__(self,
start: Point,
left: Optional[LeftEvent],
original_start: Point) -> None:
self.left, self._original_start, self._start = (left, original_start,
start)
self._tangents = [] # type: List[Event]

__repr__ = recursive_repr()(generate_repr(__init__))

def register_tangent(self, tangent: 'Event') -> None:
assert self.start == tangent.start
assert tangent not in self._tangents
self._tangents.append(tangent)

0 comments on commit d4a1e6a

Please sign in to comment.