17.25 Case Studies

Individual geometry algorithms are useful, but real systems usually require several of them working together.

17.25 Case Studies

Problem

Individual geometry algorithms are useful, but real systems usually require several of them working together.

A practical geometry task rarely says only:

compute a convex hull

or:

test one segment intersection

Instead, it asks for a result such as:

find all buildings inside a map region
detect collisions among moving objects
measure total occupied area
simplify a point cloud
plan a path through obstacles

These tasks combine predicates, indexing, sweep-line methods, polygon operations, and numeric policies.

This section walks through common end-to-end patterns.

Case Study 1: Finding Points Inside a Region

Suppose you have:

millions of points
one polygonal region

and need to find all points inside the polygon.

A brute-force solution applies point-in-polygon to every point:

O(nm)

where:

n = number of points
m = number of polygon vertices

A better pipeline is:

polygon bounding box
    ↓
spatial index query
    ↓
point-in-polygon exact test

Implementation sketch:

def points_inside_region(index, polygon):
    box = polygon.bounding_box

    candidates = index.range_query(box)

    return [
        point
        for point in candidates
        if classify_point_in_polygon(point, polygon) != "outside"
    ]

The spatial index removes most irrelevant points. The exact predicate preserves correctness.

Case Study 2: Broad-Phase Collision Detection

A game or simulation may need to detect possible collisions among many moving objects.

Checking every pair costs:

O(n²)

Instead, use a spatial hash.

Pipeline:

clear hash
insert object bounding boxes
generate candidate pairs
run exact collision tests

Sketch:

def collision_pairs(objects, cell_size):
    cells = build_spatial_hash(objects, cell_size)
    tested = set()
    result = []

    for bucket in cells.values():
        for i in range(len(bucket)):
            for j in range(i + 1, len(bucket)):
                a = bucket[i]
                b = bucket[j]

                key = pair_key(a, b)

                if key in tested:
                    continue

                tested.add(key)

                if exact_collision(a, b):
                    result.append((a, b))

    return result

The spatial hash provides candidates. The exact collision predicate decides the result.

This is the broad-phase then narrow-phase pattern.

Case Study 3: Measuring Map Coverage

Suppose a map system needs the total area covered by rectangular tiles, imagery patches, or bounding boxes.

Naively summing areas double-counts overlaps.

Use the rectangle union algorithm:

rectangles
    ↓
vertical sweep events
    ↓
active y-interval coverage
    ↓
area accumulation

For moderate inputs, recompute merged intervals at each step.

For large inputs, use coordinate compression plus a segment tree.

The important invariant is:

active y-intervals describe coverage between previous_x and current_x

This invariant keeps the implementation correct.

Case Study 4: Cleaning a Point Cloud

Point clouds often contain duplicates or near-duplicates.

Goal:

merge points closer than eps

Pipeline:

spatial hash with cell size eps
    ↓
nearby-cell search
    ↓
exact distance check
    ↓
merge or keep

Sketch:

def deduplicate_points(points, eps):
    index = SpatialHash(eps)
    result = []

    for point in points:
        duplicate = None

        for candidate in index.radius_query(point, eps):
            duplicate = candidate
            break

        if duplicate is None:
            result.append(point)
            index.insert(point)

    return result

This avoids comparing each point with every previously accepted point.

Case Study 5: Building an Autocomplete Map Boundary

Suppose you receive unordered GPS samples around a boundary and want a simple enclosing region.

One possible approximation:

points
    ↓
deduplicate
    ↓
convex hull
    ↓
optional rotating-calipers measurement

Sketch:

def enclosing_boundary(points):
    cleaned = deduplicate_exact(points)
    hull = convex_hull(cleaned)

    return hull

This produces a convex enclosure, not the true boundary. It is useful when the application needs a conservative outer shape.

For a concave boundary, use a different method, such as alpha shapes or polygon reconstruction.

The important lesson is to document the geometric meaning of the output.

Case Study 6: Nearest Facility Lookup

Given many facilities and many query locations, find the nearest facility for each query.

Possible choices:

KD-tree
Voronoi diagram
spatial hash

If you only need nearest-neighbor queries:

KD-tree

is usually simpler.

If you need visible service regions:

Voronoi diagram

is appropriate.

Pipeline with a KD-tree:

tree = build_kdtree(facilities)

for query in queries:
    facility, distance2 = nearest(tree, query)

Pipeline with Voronoi:

build diagram
locate query point in cell
return owning site

Both solve nearest-facility lookup, but they optimize for different needs.

Case Study 7: Polygon Intersection Queries

Suppose you have thousands of polygons and need to find which ones intersect a query polygon.

Pipeline:

R-tree over polygon bounding boxes
    ↓
candidate polygons
    ↓
exact polygon intersection

Sketch:

def intersecting_polygons(index, query_polygon):
    candidates = index.search(query_polygon.bounding_box)

    return [
        polygon
        for polygon in candidates
        if polygons_intersect(polygon, query_polygon)
    ]

This is a standard spatial-database pattern.

The index avoids scanning every polygon. The exact predicate avoids false positives.

Case Study 8: Segment Intersection Report

Given many line segments, report all intersections.

Brute force:

O(n²)

Sweep-line approach:

events
    ↓
active segment order
    ↓
neighbor checks
    ↓
reported intersections

For a small script, brute force may be acceptable and easier to verify.

For large input, use a sweep-line algorithm such as Bentley-Ottmann.

Decision rule:

small n or one-off tool: brute force
large n or repeated workload: sweep line

Do not pay implementation complexity unless the input size requires it.

Case Study 9: Mesh Preprocessing

A mesh pipeline often needs to:

remove duplicate vertices
detect degenerate triangles
compute triangle areas
build adjacency
validate orientation

Geometry primitives used:

distance comparison
area2
orientation
spatial hashing
bounding boxes

Sketch:

def triangle_area2(a, b, c):
    return abs(orientation(a, b, c))

Degenerate triangle:

def is_degenerate_triangle(a, b, c):
    return triangle_area2(a, b, c) == 0

If coordinates are floating-point, use a robust sign policy instead of direct equality.

Case Study 10: Path Planning with Obstacles

A simple visibility-graph planner uses:

obstacle vertices
start point
goal point
segment intersection
shortest path

Pipeline:

collect vertices
connect visible pairs
run Dijkstra

Visibility test:

def visible(a, b, obstacles):
    candidate = Segment(a, b)

    for obstacle in obstacles:
        for edge in obstacle.edges():
            if segments_intersect(candidate.start, candidate.end, edge.start, edge.end):
                return False

    return True

This is conceptually simple but can be slow.

Spatial indexes can speed up obstacle-edge lookup.

The final shortest path step belongs to graph algorithms, showing how geometry and graph algorithms combine.

Choosing the Right Tool

A useful decision table:

Task Typical Tool
Outer boundary of points Convex hull
Maximum distance on hull Rotating calipers
Total rectangle coverage Sweep line
Nearby point lookup KD-tree or spatial hash
Polygon containment Point-in-polygon
Collision broad phase Spatial hash or BVH
Spatial database query R-tree
Nearest service regions Voronoi diagram
Mesh triangles Delaunay triangulation
Exact segment crossing Orientation tests

The table is a starting point, not a rulebook. Data size, update frequency, and numeric requirements still matter.

Building Reliable Pipelines

A reliable geometry pipeline usually follows this order:

normalize input
validate degeneracies
build cheap filters
run exact predicates
cache derived data
test boundary cases

Example:

raw polygons
    ↓
remove duplicate vertices
    ↓
compute bounding boxes
    ↓
build R-tree
    ↓
query candidates
    ↓
exact intersection

Each stage has a clear responsibility.

Common Pitfalls

Using a Heavy Algorithm Too Early

A brute-force reference may be enough for small input and is easier to test.

Skipping the Exact Predicate

Indexes and bounding boxes produce candidates, not final answers.

Forgetting Numeric Policy

Integer, floating-point, and rational geometry require different assumptions.

Returning an Approximation Without Saying So

A convex hull is not a concave boundary.

A bounding box is not a polygon.

A spatial hash result is not a nearest-neighbor proof.

Mixing Algorithmic Domains

Many end-to-end tasks combine geometry with graphs, sorting, dynamic programming, or indexing. Keep those boundaries explicit.

Discussion

Computational geometry becomes practical when algorithms are composed into pipelines. A spatial index narrows the search, a bounding box rejects impossible cases, a predicate makes the exact decision, and a graph or optimization algorithm may consume the result.

The central lesson of this chapter is consistency. Use a single orientation convention. Define boundary behavior. Separate candidates from exact answers. Preserve numeric assumptions. Test degenerate cases deliberately. These habits matter more than any single algorithm.

Geometry is unforgiving because tiny decisions can alter topology. It is also highly reusable because the same primitives appear everywhere. Once you trust your predicates, indexes, and boundary conventions, the rest of the system becomes much easier to build.