Skip to content

Conversation

@urschrei
Copy link
Member

@urschrei urschrei commented Dec 27, 2025

Adds the Voronoi trait with methods to compute Voronoi diagrams:

  • voronoi_edges(): Returns line segments from unconstrained Delaunay triangulation
  • voronoi_edges_with_params(): Same with configurable tolerance and clipping
  • voronoi_cells(): Returns Vec<Polygon> with cells clipped to padded bounding box
  • voronoi_cells_with_params(): Same with configurable tolerance and clipping

Configuration is via VoronoiParams builder:

  • .tolerance(T): Snap nearby points before triangulation
  • .clip(VoronoiClip): Control clipping behaviour
    • Padded: 50% padding around input bbox (default, matches PostGIS)
    • Envelope: Clip to exact input bounding box
    • Polygon(&Polygon): Clip to custom boundary

Cells are constructed by iterating over Spade's voronoi_faces() and converting vertices (circumcenters for inner, bounding box intersections for outer) into polygon rings.

  • I agree to follow the project's code of conduct.
  • I added an entry to CHANGES.md if knowledge of this change could be valuable to users.

Depends on #1486, please review / merge that first.

@urschrei
Copy link
Member Author

london_postcodes

Some example output. I haven't written a benchmark, but single-core perf on my M2 is around 5.5 seconds to produce a clipped Voronoi diagram from 2693630 input vertices. That drops to around 1.3 seconds (including the snapping step) if you use a tolerance of 100.0 (points <= 100 metres apart are snapped together, resulting in fewer, larger Voronoi cells).

@urschrei urschrei force-pushed the split_delaunay_trait branch from 85e0879 to 328155c Compare December 27, 2025 18:07
@urschrei urschrei force-pushed the voronoi branch 2 times, most recently from 66eb15a to 5604e7b Compare December 28, 2025 15:08
// - `CoordsIter` trait for vertex iteration
// The `line_intersection` and `Euclidean::distance` functions from geo are already used here.
//
// TODO: Cell clipping currently uses BooleanOps intersection. Alternative approaches that
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are possible TODOs for follow-up work; I'm fairly happy with the existing clipping mechanisms, which took a long time to get into their current state. I suspect they could be further simplified if we could clip LineString geometries using BooleanOps, but I don't want to hold this up while we think through that.

Ok(raw_cells
.into_iter()
.flat_map(|cell| {
// Skip intersection if cell is entirely within clip bounds
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds the Voronoi trait with methods to compute Voronoi diagrams:

- voronoi_edges(): Returns line segments from unconstrained Delaunay triangulation
- voronoi_edges_with_params(): Same with configurable tolerance and clipping
- voronoi_cells(): Returns Vec<Polygon<T>> with cells clipped to padded bounding box
- voronoi_cells_with_params(): Same with configurable tolerance and clipping

Configuration is via VoronoiParams builder:
- .tolerance(T): Snap nearby points before triangulation
- .clip(VoronoiClip): Control clipping behaviour
  - Padded: 50% padding around input bbox (default, matches PostGIS)
  - Envelope: Clip to exact input bounding box
  - Polygon(&Polygon<T>): Clip to custom boundary

Cells are constructed by iterating over Spade's voronoi_faces() and
converting vertices (circumcenters for inner, bounding box intersections
for outer) into polygon rings.

Signed-off-by: Stephan Hügel <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants