The Project
A GIS system answers a small, recurring set of questions about map layers — parcels, zoning districts, flood plains, roads — that were each built independently and now need to be combined and queried together: "which zoning district is this parcel in?" (overlay), "what's inside this map viewport?" (range query), and "which parcel did the user just click on?" (point location). None of these questions needs a new algorithm. Every one of them is a direct application of a part already covered in this series, and the capstone's actual deliverable is the shared representation that lets all three questions reuse the same underlying structures instead of re-deriving them per query.
Core Insight: every layer in this engine — however it was constructed — ends up stored as the same thing: a DCEL, indexed by a range tree for window queries and a trapezoidal map for point location. The overlay operation and both query types are then just traversals or face-selections over that one representation, not three separate subsystems.
Design Challenge
Given two independently digitized polygon layers — 40 zoning districts and 25,000 parcels — design a single SpatialLayer class whose methods support building a layer from raw polygons, overlaying two layers into a third, answering "what's in this rectangle?", and answering "which face contains this click?" — all without re-triangulating or re-indexing between calls.
Architecture
The engine has exactly four stages, each one a direct reuse of an earlier part:
- Ingest. Parse each layer's raw polygons into a DCEL (Part 7), normalizing orientation and rejecting or repairing self-intersections using the robust orientation and segment-intersection predicates from Parts 1 and 6.
- Overlay. To combine two layers, sweep-line-node their boundaries together (Part 6), merge the results into one DCEL, propagate inside/outside labels for each source layer across every face, and select faces per the requested Boolean operation (Part 8) — union for "either layer," intersection for "both," and so on.
- Index. Build a 2D range tree over each layer's face bounding boxes for window queries (Part 11), and a randomized incremental trapezoidal map over the same DCEL's edges for point-location queries (Part 15).
- Query. A window query walks the range tree's canonical subtrees; a point-location query walks the trapezoidal map's history DAG to a leaf, then reads that leaf's face label straight off the DCEL.
| Capability | Reused part | What it actually does here |
|---|---|---|
| Robust predicates | Part 1 | Every orientation/intersection test downstream depends on these being exactly right |
| Boundary noding | Part 6 (sweep line) | Splits two layers' edges at every crossing before they're merged |
| Topology storage | Part 7 (DCEL) | One shared representation for every layer and every overlay result |
| Layer combination | Part 8 (overlay) | Face labeling + a Boolean truth table select the output faces |
| Window queries | Part 11 (range tree) | "What's visible in this viewport?" |
| Point location | Part 15 (trapezoidal map) | "Which parcel/district did the user click?" |
Why This Is the Capstone, Not Just a Recap
Building each piece in isolation is what Parts 1–15 already did. The capstone's real design problem is keeping all three query paths consistent with a single evolving DCEL: an overlay operation replaces faces, which means the range tree's bounding boxes and the trapezoidal map's history DAG both need to reflect the new topology before the next query runs. Two disciplined choices make this tractable:
- Rebuild indexes, don't patch them, after every overlay. Both the range tree (Part 11) and the trapezoidal map (Part 15) are built for static input; incremental repair of either after an overlay is far more intricate than a
O(n log n)rebuild from the merged DCEL's faces, and in a GIS pipeline overlay operations are batch steps, not per-click events. - Never let two different layers disagree about a boundary that both claim to share. A parcel boundary that coincides with a zoning-district boundary must be noded into the same vertex and edge chain in the merged DCEL (Part 6's shared-endpoint and collinear-overlap policies), or the overlay's face labeling silently produces sliver faces along every shared edge — the single most common visible bug in real GIS overlay tools.
Trace a Query End to End
A parcel layer and a flood-plain layer are overlaid with intersection to produce "parcels at risk." Trace what happens to a parcel whose boundary exactly coincides with the flood plain's boundary along one edge: which stage (noding, labeling, or face selection) is responsible for deciding whether that shared edge produces a sliver intersection face or correctly contributes no extra area? What earlier part's degeneracy policy does that decision depend on?
What to Build
A minimal version of this capstone needs only three real GIS layers (say, parcels, zoning, and a flood-plain polygon), one overlay operation, one window query, and one point-location query — enough to prove that all three query types genuinely share the ingest and overlay stages rather than reimplementing geometry per query type. The full system this capstone sketches — multiple simultaneous layers, incremental updates, and a real tile-serving front end — is a natural next step once the shared-representation discipline above is in place.