Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Computational Geometry  ›  Lesson

Do These Two Routes Cross?

Segment Intersection 28 minFour orientation tests decide most segment crossings, but a special case — two segments overlapping along the same line — needs a bounding-box check, not just a sign comparison
You're building a piece ofMeridian — service areas & coverage
This piece — segments_intersect(): Tells a dispatcher whether two proposed routes cross, including the case where they run along the same line.
Scenario A dispatcher is about to add a new delivery route and needs to know, before committing it, whether it crosses a route that is already running.
Your task
Build segments_intersect(p1, p2, p3, p4). Segment one runs p1 to p2; segment two runs p3 to p4. Return True if the two segments share any point at all — a proper crossing, a touch at an endpoint, OR an overlap where they run along the same line. Use orientation(p3, p4, p1), orientation(p3, p4, p2), orientation(p1, p2, p3) and orientation(p1, p2, p4): if the first two differ AND the last two differ, the segments properly cross. Otherwise, whenever one of those four is 0, check with on_segment whether the collinear point actually lies within the other segment's bounding box. Example: segments_intersect((0,0), (4,0), (2,0), (6,0)) -> True (they overlap along the same line).

Do These Two Routes Cross?

In short: two segments p1-p2 and p3-p4 cross if p1 and p2 fall on opposite sides of the line through p3 and p4, AND p3 and p4 fall on opposite sides of the line through p1 and p2. That's four calls to last lesson's orientation and one comparison each way. The naive version of that test has a gap: two segments that run along the same line and overlap give every orientation a 0, which the naive test reads as "no crossing" — wrong. Closing that gap needs one more check: when an orientation comes back 0, look at whether the point actually falls inside the other segment's span.

Two segments crossing in an X, with the four orientation values badged at their corners — d1 and d2 on opposite sides of segment p3-p4, d3 and d4 on opposite sides of segment p1-p2 — and a second pair of segments lying on the same horizontal line, overlapping, with all four badges reading zero and a question mark over them

A clean X crossing

Two Meridian delivery routes are proposed, and the dispatcher wants to know if they cross before either driver sets off:

step through it
1def orientation(a, b, c):
2 cross = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
3 if cross > 0:
4 return 1
5 if cross < 0:
6 return -1
7 return 0
8 
9def on_segment(p, a, b):
10 return (min(a[0], b[0]) <= p[0] <= max(a[0], b[0]) and
11 min(a[1], b[1]) <= p[1] <= max(a[1], b[1]))
12 
13def segments_intersect(p1, p2, p3, p4):
14 d1 = orientation(p3, p4, p1)
15 d2 = orientation(p3, p4, p2)
16 d3 = orientation(p1, p2, p3)
17 d4 = orientation(p1, p2, p4)
18 if d1 != d2 and d3 != d4:
19 return True
20 if d1 == 0 and on_segment(p1, p3, p4):
21 return True
22 if d2 == 0 and on_segment(p2, p3, p4):
23 return True
24 if d3 == 0 and on_segment(p3, p1, p2):
25 return True
26 if d4 == 0 and on_segment(p4, p1, p2):
27 return True
28 return False
29 
30route_a = ((0, 0), (4, 4))
31route_b = ((0, 4), (4, 0))
32print(segments_intersect(*route_a, *route_b))

route_a's endpoints land on opposite sides of route_b's line, and route_b's endpoints land on opposite sides of route_a's line too — both conditions hold, so d1 != d2 and d3 != d4 is already True and the function returns on the first check, with no collinear case needed at all.

The case the naive test gets wrong

Here is the four-orientation test without the collinear fallback, on two routes that both run along the same east-west line, overlapping in the middle:

What does this print?
def orientation(a, b, c):
    cross = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
    if cross > 0:
        return 1
    if cross < 0:
        return -1
    return 0

def naive_crossing(p1, p2, p3, p4):
    d1 = orientation(p3, p4, p1)
    d2 = orientation(p3, p4, p2)
    d3 = orientation(p1, p2, p3)
    d4 = orientation(p1, p2, p4)
    return d1 != d2 and d3 != d4

print(naive_crossing((0, 0), (4, 0), (2, 0), (6, 0)))

The two routes genuinely overlap for two whole kilometres, and the naive test waves it through. on_segment closes this gap: whenever an orientation comes back 0, it means that point is collinear with the other segment, and on_segment then asks the only remaining question — does it actually fall within that segment's span, or off one end of it?

broken — fix it

Both lines print False, but the first pair genuinely overlaps. Add the missing collinear-overlap checks.

Quick practice

What does this print?
def orientation(a, b, c):
    cross = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
    if cross > 0:
        return 1
    if cross < 0:
        return -1
    return 0

p1, p2, p3, p4 = (0, 0), (4, 0), (0, 2), (4, 2)
print(orientation(p3, p4, p1) != orientation(p3, p4, p2))
What does this print?
def on_segment(p, a, b):
    return (min(a[0], b[0]) <= p[0] <= max(a[0], b[0]) and
            min(a[1], b[1]) <= p[1] <= max(a[1], b[1]))

print(on_segment((4, 4), (0, 0), (4, 4)))

🎯 Your turn

Build segments_intersect(p1, p2, p3, p4): return True if the two segments share any point — a proper crossing, a touching endpoint, or a collinear overlap.

  • segments_intersect((0,0), (4,4), (0,4), (4,0)) → True
  • segments_intersect((0,0), (4,0), (2,0), (6,0)) → True
  • segments_intersect((0,0), (4,0), (5,0), (8,0)) → False

Hint: compute d1 = orientation(p3, p4, p1), d2 = orientation(p3, p4, p2), d3 = orientation(p1, p2, p3), d4 = orientation(p1, p2, p4). If d1 != d2 and d3 != d4, return True. Otherwise, check on_segment for each d that came back 0.

Press ▶ Run, try the Live App chips, then ✓ Check.

Practice — level up

0/5 solved

Solve each in its own editor — Run to try, Check to grade. Stuck? Reveal a Hint, or the full Solution + walkthrough.

Warm-upIs this collinear point actually on the segment?

Write on_segment(p, a, b). You're told p already lies on the infinite line through a and b; return True only if p also falls within a and b's bounding box (so it's on the segment itself, not off one end of it). on_segment((2, 0), (0, 0), (4, 0)) -> True.

segment-intersection
DrillThe naive test, and the gap it leaves

Write proper_crossing(p1, p2, p3, p4) using ONLY the four-orientation test (no collinear handling): return True if d1 != d2 and d3 != d4, where d1..d4 are the same four orientation calls as the task. proper_crossing((0,0), (4,4), (0,4), (4,0)) -> True.

orientationsegment-intersection
BuildHow many pairs of routes cross?

Write count_crossings(segments), where segments is a list of (p1, p2) pairs. Return the number of DISTINCT pairs of segments that intersect, checking every pair once. count_crossings([((0,0),(4,4)), ((0,4),(4,0)), ((2,0),(6,0)), ((5,5),(8,8))]) -> 2.

segment-intersection
BossWhich pair crosses first?

Write find_crossing_pair(segments), returning the FIRST pair of indices [i, j] with i < j whose segments intersect, scanning pairs in the same order as count_crossings. Return None if no pair crosses. find_crossing_pair([((0,0),(4,4)), ((0,4),(4,0)), ((2,0),(6,0))]) -> [0, 1].

segment-intersection
CapstoneCan this new route be added safely?

Write safe_to_add(existing, candidate), where existing is a list of (p1, p2) segments already running and candidate is one new (p3, p4) segment. Return True only if candidate crosses NONE of the existing segments. safe_to_add([((0,0),(4,0))], ((0,2),(4,2))) -> True.

segment-intersection
segments_intersect.py
Call the function you wrote — just like your app's frontend would. Edit the inputs and hit Call.
segments_intersect(p1, p2, p3, p4) → boolTrue if segment p1-p2 and segment p3-p4 share any point — a proper crossing, a touching endpoint, or a collinear overlap.
try:
returned
Press Run to execute your code.
Press Check to run the tests for this lesson.