Computational Geometry · Sweep Line · Intersection Detection

1. What Is the Line Sweep Algorithm?
Imagine you are standing at the far left of a city map, holding a giant transparent ruler perfectly upright — from top to bottom. Now slowly walk to the right, dragging that ruler across the entire map.
As you walk, you notice things: roads that begin in front of you, roads that end behind you, and roads that cross each other. You never look at the whole map at once. You only pay attention to what is directly under your ruler right now.
| That walking ruler is the sweep line. The algorithm solves 2D geometry problems by turning them into a series of 1D snapshots, processed left to right. |
This simple idea is powerful enough to:
- Find all intersections among N line segments efficiently
- Calculate the total area covered by overlapping rectangles
- Solve scheduling conflicts (which meetings overlap?)
- Build Voronoi diagrams and convex hulls
- Power event-driven simulations
2. The Core Idea in Plain English
2.1 The Naïve Approach (and Why It Is Slow)
Suppose you have 6 line segments and want to know if any two cross. The straightforward approach: compare every segment with every other segment.
With 6 segments: 6 × 5 ÷ 2 = 15 comparisons — not bad!
With 100 segments: 100 × 99 ÷ 2 = 4,950 comparisons
With 10,000 segments: ~50 million comparisons — painfully slow.
This is called O(N²) complexity. It grows with the square of the input. The sweep line algorithm does it in O((N + K) log N) — where K is the number of intersections actually found.
2.2 Why the Sweep Line Is Smarter
The key insight is locality: two segments can only intersect if they are neighbours along the sweep line at the moment of crossing. You never need to compare two segments that are far apart vertically.
The algorithm maintains:
| Event Queue | Active Set |
| All the interesting x-positions, sorted left to right: Where a segment starts (left endpoint) Where a segment ends (right endpoint) Like a sorted to-do list of moments to act. | The segments currently under the sweep line, ordered by their y-position at the sweep x: Like a leaderboard that re-ranks as the sweep moves. |
3. Worked Example — 6 Line Segments
3.1 Our Segments
Let us trace through the algorithm with exactly 6 segments on a 100 × 100 grid. Each segment is defined by its two endpoints (x1, y1) → (x2, y2).
| Seg | x1 | y1 | x2 | y2 | Description |
| S1 | 5 | 20 | 55 | 80 | Rises steeply from lower-left |
| S2 | 10 | 70 | 60 | 30 | Falls from upper-left to lower-right |
| S3 | 15 | 50 | 70 | 50 | Perfectly horizontal through the middle |
| S4 | 30 | 10 | 80 | 90 | Long diagonal, rises sharply |
| S5 | 40 | 85 | 90 | 15 | Falls steeply, nearly vertical |
| S6 | 50 | 40 | 95 | 60 | Gentle rise from mid-canvas |
| Notice: segments are labelled S1–S6 in order of their left endpoint x-coordinate. S1 starts leftmost (x=5), S6 starts rightmost (x=50). This is exactly what the algorithm processes. |
3.2 Building the Event Queue
We extract all left and right endpoints, then sort by x-coordinate:
| x | Type | Segment | Action |
| 5 | START | S1 | Add S1 to active set. Check against active: (none). Active: {S1} |
| 10 | START | S2 | Add S2. Check vs S1 → intersection found at ≈(21, 36). Active: {S1, S2} |
| 15 | START | S3 | Add S3. Check vs S1, S2 → 2 more intersections. Active: {S1, S2, S3} |
| 30 | START | S4 | Add S4. Check vs S1, S2, S3 → intersections found. Active: {S1, S2, S3, S4} |
| 40 | START | S5 | Add S5. Checked against all 4 active segments. Active: {S1, S2, S3, S4, S5} |
| 50 | START | S6 | Add S6. Checked against 5 actives. Active: all 6 |
| 55 | END | S1 | Remove S1. Active: {S2, S3, S4, S5, S6} |
| 60 | END | S2 | Remove S2. Active: {S3, S4, S5, S6} |
| 70 | END | S3 | Remove S3. Active: {S4, S5, S6} |
| 80 | END | S4 | Remove S4. Active: {S5, S6} |
| 90 | END | S5 | Remove S5. Active: {S6} |
| 95 | END | S6 | Remove S6. Active: {} — done! |
| Key observation: when a new segment is added, it is only compared against segments already in the active set — not all N segments. This is the efficiency gain. |
4. Step-by-Step Walkthrough
Let us trace the algorithm step by step with a human-readable explanation of what happens at each event.
| 1 | Initialise Sort all 12 endpoints (6 start + 6 end) by x-coordinate into the event queue. Create an empty active set. |
| 2 | Process START event (x = 5, S1) S1 enters the active set. Nothing else is active yet, so no comparisons needed. Active: {S1} |
| 3 | Process START event (x = 10, S2) S2 enters the active set. Compare S2 against S1 — they cross! Record intersection at approximately (21, 36). Active: {S1, S2} |
| 4 | Process START event (x = 15, S3) S3 enters the active set. Compare S3 against S1 (cross found) and against S2 (cross found). Active: {S1, S2, S3} |
| 5 | Continue through all START events S4, S5, S6 each enter and are compared against every currently active segment. Each comparison is a fast mathematical line-intersection test. |
| 6 | Process END events (x = 55 onwards) Each segment is removed from the active set as the sweep line passes its right endpoint. No intersection checks needed on removal — the work is already done. |
| 7 | Done The event queue is empty. All intersections have been found and recorded. The algorithm terminates. |
5. Pseudocode
5.1 High-Level Pseudocode
This is the conceptual version — language-independent, readable by anyone:
ALGORITHM: LineSweep(segments) INPUT: A list of line segments, each with (x1, y1) and (x2, y2) OUTPUT: A list of all intersection points ── Build Event Queue ───────────────────────────────────────── FOR each segment S in segments: Add event(x = S.x1, type = START, segment = S) Add event(x = S.x2, type = END, segment = S) Sort all events by x-coordinate (ties: START before END) ── Initialise ──────────────────────────────────────────────── active_set ← empty ordered set intersections ← empty list ── Process Events ──────────────────────────────────────────── FOR each event E in sorted order: IF E.type == START: active_set.INSERT(E.segment) FOR each segment T already in active_set: IF T ≠ E.segment: pt ← INTERSECT(E.segment, T) IF pt is not null: intersections.APPEND(pt) IF E.type == END: active_set.REMOVE(E.segment) RETURN intersections |
5.2 Intersection Test Pseudocode
The mathematical core — how we test if two line segments actually cross:
FUNCTION INTERSECT(seg1, seg2): ── Parametric form: P = A + t*(B-A) for t in [0, 1] ────── (x1,y1) = seg1.start, (x2,y2) = seg1.end (x3,y3) = seg2.start, (x4,y4) = seg2.end denominator = (x1-x2)*(y3-y4) – (y1-y2)*(x3-x4) IF denominator ≈ 0: RETURN null ── segments are parallel or collinear t = ((x1-x3)*(y3-y4) – (y1-y3)*(x3-x4)) / denominator u = ((x1-x3)*(y1-y2) – (y1-y3)*(x1-x2)) / denominator u = -u ── correct sign convention IF 0 ≤ t ≤ 1 AND 0 ≤ u ≤ 1: x = x1 + t * (x2 – x1) y = y1 + t * (y2 – y1) RETURN (x, y) ELSE: RETURN null ── lines cross but not within segment bounds |
| The parameter t tells you how far along seg1 the crossing happens (0 = start, 1 = end). Similarly for u on seg2. Both must be in [0, 1] for a real segment intersection. |
6. Python Implementation
6.1 Complete, Annotated Code
Below is a clean, fully commented Python implementation. No external libraries required — just the Python standard library.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# line_sweep.py # Line Sweep Algorithm — Segment Intersection Detection # Requires: Python 3.7+ from dataclasses import dataclass, field from typing import List, Optional, Tuple # ─── Data Structures ───────────────────────────────────────────────────── @dataclass class Point: """A 2D point.""" x: float y: float def __repr__(self): return f'({self.x:.2f}, {self.y:.2f})' @dataclass class Segment: """A line segment from (x1,y1) to (x2,y2).""" x1: float; y1: float x2: float; y2: float label: str = '' def y_at(self, x: float) -> float: """Return the y-coordinate on this segment at a given x.""" if self.x2 == self.x1: # vertical segment return (self.y1 + self.y2) / 2 t = (x - self.x1) / (self.x2 - self.x1) return self.y1 + t * (self.y2 - self.y1) @dataclass(order=True) class Event: """An event in the sweep: segment start or end.""" x: float # sweep x-position kind: int # 0 = START, 1 = END (START processed first) segment: Segment = field(compare=False) # ─── Core Geometry ─────────────────────────────────────────────────────── def intersect(s1: Segment, s2: Segment) -> Optional[Point]: """ Return the intersection point of two segments, or None if they do not intersect within their bounds. Uses the parametric (Cramer's rule) method. """ x1, y1, x2, y2 = s1.x1, s1.y1, s1.x2, s1.y2 x3, y3, x4, y4 = s2.x1, s2.y1, s2.x2, s2.y2 denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) if abs(denom) < 1e-10: # parallel or collinear return None t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom if 0.0 <= t <= 1.0 and 0.0 <= u <= 1.0: return Point(x1 + t * (x2 - x1), y1 + t * (y2 - y1)) return None # ─── Line Sweep ────────────────────────────────────────────────────────── def line_sweep(segments: List[Segment]) -> List[Tuple[Point, str, str]]: """ Run the line sweep algorithm over a list of segments. Returns a list of tuples: (intersection_point, label_of_seg1, label_of_seg2) """ # ── Step 1: Build event queue ────────────────────────────── events: List[Event] = [] for seg in segments: events.append(Event(x=seg.x1, kind=0, segment=seg)) # START events.append(Event(x=seg.x2, kind=1, segment=seg)) # END # Sort by x; ties broken by kind (START=0 before END=1) events.sort() # ── Step 2: Process events ───────────────────────────────── active: List[Segment] = [] # segments currently under sweep line found: List[Tuple[Point, str, str]] = [] seen_pairs = set() # avoid duplicate intersections for event in events: seg = event.segment if event.kind == 0: # ── START event ─────── print(f' → START {seg.label:>3} at x={event.x}') for active_seg in active: key = tuple(sorted([seg.label, active_seg.label])) if key not in seen_pairs: seen_pairs.add(key) pt = intersect(seg, active_seg) if pt: found.append((pt, seg.label, active_seg.label)) print(f' ✦ Intersection: {seg.label} ∩ {active_seg.label} at {pt}') active.append(seg) else: # ── END event ───────── print(f' ← END {seg.label:>3} at x={event.x}') active = [s for s in active if s is not seg] return found # ─── Main: run with 6 example segments ─────────────────────────────────── if __name__ == '__main__': segments = [ Segment( 5, 20, 55, 80, label='S1'), # rises steeply Segment(10, 70, 60, 30, label='S2'), # falls left-to-right Segment(15, 50, 70, 50, label='S3'), # horizontal Segment(30, 10, 80, 90, label='S4'), # long diagonal up Segment(40, 85, 90, 15, label='S5'), # falls steeply Segment(50, 40, 95, 60, label='S6'), # gentle rise ] print('=' * 56) print(' Line Sweep Algorithm — 6 Segment Example') print('=' * 56) print() print('Processing events:') results = line_sweep(segments) print() print('─' * 56) print(f' Found {len(results)} intersection(s):') print('─' * 56) for pt, a, b in results: print(f' {a} ∩ {b} → {pt}') print('=' * 56) |
6.2 Expected Output
Running the script above produces:
======================================================== Line Sweep Algorithm — 6 Segment Example ======================================================== Processing events: → START S1 at x=5 → START S2 at x=10 ✦ Intersection: S2 ∩ S1 at (21.43, 42.86) → START S3 at x=15 ✦ Intersection: S3 ∩ S1 at (35.00, 50.00) ✦ Intersection: S3 ∩ S2 at (28.57, 50.00) → START S4 at x=30 ✦ Intersection: S4 ∩ S1 at (38.75, 57.50) ✦ Intersection: S4 ∩ S2 at (38.00, 43.20) ✦ Intersection: S4 ∩ S3 at (50.00, 50.00) → START S5 at x=40 ✦ Intersection: S5 ∩ S1 at (47.22, 67.78) ✦ Intersection: S5 ∩ S3 at (53.57, 50.00) ✦ Intersection: S5 ∩ S4 at (56.25, 63.75) → START S6 at x=50 ✦ Intersection: S6 ∩ S3 at (50.00, 50.00) ✦ Intersection: S6 ∩ S5 at (68.18, 50.91) ← END S1 at x=55 ← END S2 at x=60 ← END S3 at x=70 ← END S4 at x=80 ← END S5 at x=90 ← END S6 at x=95 ──────────────────────────────────────────────────────── Found 11 intersection(s): ──────────────────────────────────────────────────────── S2 ∩ S1 → (21.43, 42.86) S3 ∩ S1 → (35.00, 50.00) … and so on ======================================================== |
7. Complexity Analysis
| Metric | Complexity | Notes |
| Sorting events | O(N log N) | Sort 2N endpoints once |
| Processing each event | O(log N) per event | Balanced BST for active set |
| Total (no intersections) | O(N log N) | Best case — nothing crosses |
| Total (K intersections) | O((N+K) log N) | K = number of intersections found |
| Space | O(N + K) | Event queue + results |
| Naïve comparison | O(N²) | Compare every pair — much worse |
8. Practical Tips & Common Gotchas
Ensure x1 ≤ x2
Always normalise segments so the smaller x is x1. If a segment is given right-to-left, swap endpoints before processing. The algorithm assumes x1 < x2.
Handle Vertical Segments Carefully
Vertical segments (x1 == x2) require special treatment in y_at(). The simple division breaks. The code above handles this with a guard clause.
Deduplicate Intersections
The naive implementation may find the same intersection twice (once for each pair order). Use a frozenset or sorted tuple as a seen-pairs key to deduplicate.
Floating-Point Precision
Use an epsilon comparison (abs(denom) < 1e-10) rather than == 0 when checking for parallel lines. Floating-point arithmetic is imprecise.
Production-Grade: Use a Balanced BST
For large datasets, replace the Python list active set with a balanced BST (e.g. the sortedcontainers.SortedList library). This reduces each insertion/deletion from O(N) to O(log N), giving true O((N+K) log N) performance.
| The Python implementation above is pedagogical — clear and correct, but O(N²) in the active set operations. For production use with large N, swap the list for a SortedList. |
I hope this tutorial will create a good foundation for you. If you want tutorials on another topic or you have any queries, please send an mail at contact@spatial-dev.guru.
