A Rust port of Boost.Geometry, carrying over its design philosophy: dimension-agnostic, coordinate-system-agnostic, bring-your-own-type, strategy-pluggable.
The library's algorithms are written against concept traits
(Point, Ring, Polygon, …), not concrete structs — so your own
domain types participate directly, exactly like
BOOST_GEOMETRY_REGISTER_* in C++. There is no mandatory point or
polygon type to convert into: you register the types you already have
and call the algorithms on them. That makes boost_geometry a
complement to whatever geometry types your stack already uses,
rather than a container you must migrate onto.
- Edition: Rust 2024, MSRV 1.85
- Safety:
unsafe_code = "forbid"across the whole workspace - Docs: see
docs/for the architecture, the tag-dispatch pattern, and the overlay engine
Add the dependency:
cargo add boost_geometryCoupled — with #[derive(Point)]
Derive Point on your own coordinate struct, register your own ring
and polygon types with one macro declaration each, run
is_valid_polygon on them directly, and buffer
(runnable as cargo run --example parcel_buffer):
use boost_geometry::Point;
use boost_geometry::adapt::{register_polygon, register_ring};
use boost_geometry::overlay::{JoinStrategy, buffer_convex_polygon, is_valid_polygon};
use boost_geometry::prelude::*;
// Your own geometry types — no wrapper, no conversion trait, and no
// library point type: the derive turns your struct into a Point.
#[derive(Clone, Copy, Default, Point)]
struct Coord {
x: f64,
y: f64,
}
struct Boundary {
points: Vec<Coord>,
}
struct Parcel {
outer: Boundary,
holes: Vec<Boundary>,
}
// One declaration each and the library's algorithms accept them.
register_ring!(Boundary, Coord, |s| s.points.iter());
register_polygon!(
Parcel,
Coord,
ring = Boundary,
|s| outer = &s.outer,
inners = s.holes.iter()
);
fn main() {
let c = |x, y| Coord { x, y };
// A 2×2 square parcel (clockwise, closed — the default ring convention).
let parcel = Parcel {
outer: Boundary {
points: vec![
c(0.0, 0.0),
c(0.0, 2.0),
c(2.0, 2.0),
c(2.0, 0.0),
c(0.0, 0.0),
],
},
holes: vec![],
};
// Validate the user-owned type directly.
println!("parcel valid: {:?}", is_valid_polygon(&parcel));
// Buffer it outward by 1.0 with round joins — directly on the
// user-owned type.
let mut grown = buffer_convex_polygon(
&parcel,
1.0,
JoinStrategy::Round {
points_per_circle: 360,
},
);
// Normalise ring orientation, then validate the result.
correct(&mut grown);
println!("buffered valid: {:?}", is_valid_polygon(&grown));
println!("area {:.3} -> {:.3}", area(&parcel), area(&grown));
}Output:
parcel valid: Ok(())
buffered valid: Ok(())
area 4.000 -> 15.141
De-coupled — without the derive
The derive is pure sugar: it emits the Geometry + Point
(+ PointMut) impls below. Writing them by hand is the escape hatch
for computed coordinates, packed storage, or FFI structs — same flow,
same output (runnable as cargo run --example parcel_buffer_manual):
use boost_geometry::adapt::{register_polygon, register_ring};
use boost_geometry::cs::Cartesian;
use boost_geometry::overlay::{JoinStrategy, buffer_convex_polygon, is_valid_polygon};
use boost_geometry::prelude::*;
use boost_geometry::tag::PointTag;
use boost_geometry::trait_::{Geometry, Point, PointMut};
// Your own point type, registered by hand — the escape hatch for
// computed coordinates, packed storage, or FFI structs the derive
// cannot express.
#[derive(Clone, Copy, Default)]
struct Coord {
x: f64,
y: f64,
}
impl Geometry for Coord {
type Kind = PointTag;
type Point = Coord;
}
impl Point for Coord {
type Scalar = f64;
type Cs = Cartesian;
const DIM: usize = 2;
fn get<const D: usize>(&self) -> f64 {
match D {
0 => self.x,
1 => self.y,
_ => unreachable!(),
}
}
}
// Only needed by algorithms that construct points (buffer, correct).
impl PointMut for Coord {
fn set<const D: usize>(&mut self, v: f64) {
match D {
0 => self.x = v,
1 => self.y = v,
_ => unreachable!(),
}
}
}
struct Boundary {
points: Vec<Coord>,
}
struct Parcel {
outer: Boundary,
holes: Vec<Boundary>,
}
register_ring!(Boundary, Coord, |s| s.points.iter());
register_polygon!(
Parcel,
Coord,
ring = Boundary,
|s| outer = &s.outer,
inners = s.holes.iter()
);
fn main() {
let c = |x, y| Coord { x, y };
// A 2×2 square parcel (clockwise, closed — the default ring convention).
let parcel = Parcel {
outer: Boundary {
points: vec![
c(0.0, 0.0),
c(0.0, 2.0),
c(2.0, 2.0),
c(2.0, 0.0),
c(0.0, 0.0),
],
},
holes: vec![],
};
println!("parcel valid: {:?}", is_valid_polygon(&parcel));
let mut grown = buffer_convex_polygon(
&parcel,
1.0,
JoinStrategy::Round {
points_per_circle: 360,
},
);
correct(&mut grown);
println!("buffered valid: {:?}", is_valid_polygon(&grown));
println!("area {:.3} -> {:.3}", area(&parcel), area(&grown));
}The buffered area matches the closed form for a square grown by distance d with round corners: s² + 4·s·d + π·d² = 4 + 8 + π ≈ 15.14.
What the example shows (identical for both paths above):
register_ring!/register_polygon!implement the concept traits for your structs (Rust's orphan rule forbids a blanket impl, so the macros mint it per type — the same coherence workaround theBOOST_GEOMETRY_REGISTER_*macros perform in C++). Defaults are closed, clockwise rings; both are overridable per type.is_valid_polygonchecks the OGC simple-feature rules — point count, closure, finite coordinates, spikes, self-intersections, ring orientation, hole containment — and reports the first failure as aValidityFailurevariant (Err(SelfIntersection),Err(WrongOrientation), …) rather than a barefalse.buffer_convex_polygongrows the polygon outward, rounding each corner with a circular arc (JoinStrategy::Mitergives sharp corners instead). v1 buffers points and convex polygons with positive distances.correctfixes ring closure and orientation in place — the Boostbg::correctcounterpart.
The library never sees your Coord, Boundary, or Parcel as a concrete
type. It only ever sees "some G that satisfies the Point (or Ring, or
Polygon) concept trait", and reads it through that trait's methods. Your
struct keeps its own fields, layout, and ownership; the register_*! macros
just teach the trait how to read it. That is the whole trick — the same one
BOOST_GEOMETRY_REGISTER_* performs in C++, rebuilt from ordinary Rust
generics instead of template specialisation.
The techniques below are worth stealing for any bring-your-own-type library.
The Rust features that make it work
1. A concept is a trait; the data stays yours. A geometry is anything implementing the concept trait — the library owns behaviour, you own the bytes. The read surface is tiny:
pub trait Point: Geometry<Kind = PointTag, Point = Self> {
type Scalar: CoordinateScalar; // associated type — your f64, i32, fixed-point…
type Cs: CoordinateSystem; // associated type — Cartesian / Spherical / Geographic
const DIM: usize; // dimensions, known at compile time
fn get<const D: usize>(&self) -> Self::Scalar; // read axis D
}Because Scalar and Cs are associated types (not generic parameters),
a function written fn distance<P: Point>(a: &P, b: &P) carries the scalar and
coordinate system along for free — no <P, S, Cs> soup at every call site.
2. Const generics move dimension checks to compile time. get::<const D>
takes the axis as a const generic, so p.get::<2>() on a 2-D point is a
compile error, not a runtime panic — the bound check happens once, in the
type system.
3. Zero-sized marker tags + supertraits give free category dispatch. Each
kind has a ZST tag (PointTag, RingTag, …) named by Geometry::Kind, and the
tags form a hierarchy with plain supertraits:
pub trait Polylinear: Linear {} // reproduces C++'s `polylinear_tag : linear_tag`So fn f<T: Linear>() accepts segments, linestrings, and multi-linestrings
in one signature — category dispatch with no macro, no enum, no dyn.
4. The orphan rule is sidestepped with per-type macros. Rust forbids a
blanket impl<T> Point for T, and you can't impl Point for Vec<YourCoord>
from this crate either (neither is yours). So register_ring! / #[derive(Point)]
mint one concrete impl per type at your call site, where the coherence
rules allow it — exactly the role the BOOST_GEOMETRY_REGISTER_* macros play.
5. Dispatch resolves statically, then vanishes. "One function, many kinds"
(within on a ring vs. a polygon) is solved by a Kind → zero-sized-strategy
type-level picker; every layer is a ZST and a static trait resolution, so at
-O the whole chain collapses to a single direct call — no vtable, no branch.
This is the codebase's one recurring idiom; the full mechanism (and why the
obvious impl<G: Ring> + impl<G: Polygon> approach hits E0119) is written
up in
docs/02-tag-dispatch-pattern.md.
The payoff: your types participate with no wrapper, no conversion, and no runtime cost — the generic code monomorphises straight onto your struct's own accessors.
Every capability below is a free function you call on your own registered
types — no conversion step. Adding the single boost_geometry crate brings
in all of the ✅ rows; the I/O and reprojection formats are separate crates
so a default build stays lean. The Docs link opens the rustdoc for that
item; the no_std column is the status of the crate the function lives in
(see the full matrix below).
Naming mirrors Boost.Geometry: a strategy-driven algorithm exposes a
strategy-less default (picks the right strategy for the coordinate system)
plus a _with companion that takes an explicit strategy.
Auto-generated from the source; run python3 .github/scripts/feature_table.py after adding an export.
| Function | no_std |
Docs |
|---|---|---|
| Measures — Scalar quantities of a geometry | ||
area / area_with / box_area / multi_polygon_area / ring_area |
✅ | → |
area_dyn |
✅ | → |
azimuth / azimuth_with |
✅ | → |
centroid / centroid_with |
✅ | → |
closest_points / closest_points_with |
✅ | → |
comparable_distance / comparable_distance_with / distance / distance_with |
✅ | → |
discrete_frechet_distance / discrete_frechet_distance_with |
✅ | → |
discrete_hausdorff_distance / discrete_hausdorff_distance_with |
✅ | → |
distance_dyn |
✅ | → |
length / length_with / perimeter / perimeter_with / ring_perimeter / ring_perimeter_with |
✅ | → |
length_dyn |
✅ | → |
| Spatial predicates — Boolean relationships between geometries | ||
contains_properly / crosses / overlaps / relate_matrix / relation / relate / touches |
✅ | → |
coordinate_position |
✅ | → |
covered_by / within |
✅ | → |
disjoint / disjoint_box_box |
✅ | → |
equals |
✅ | → |
intersects / intersects_reversed |
✅ | → |
within_dyn |
✅ | → |
| Boolean operations — Overlay and offset of areal geometries | ||
buffer / buffer_convex_polygon / buffer_point / buffer_with / buffer_with_strategy |
✅ | → |
difference / intersection / sym_difference / union / union_poly |
✅ | → |
line_intersection |
✅ | → |
point_on_surface |
✅ | → |
| Construction & transformation — Derive a new geometry from an existing one | ||
chaikin_smoothing |
✅ | → |
concave_hull / concave_hull_with / k_nearest_concave_hull |
✅ | → |
convex_hull |
✅ | → |
densify |
✅ | → |
destination / destination_with |
✅ | → |
envelope |
✅ | → |
envelope_dyn |
✅ | → |
expand / expand_with |
✅ | → |
line_interpolate |
✅ | → |
line_locate_point |
✅ | → |
linestring_segmentize / linestring_segmentize_with |
✅ | → |
map_coords / map_coords_in_place |
✅ | → |
minimum_rotated_rect |
✅ | → |
monotone_subdivision |
✅ | → |
rhumb_azimuth / rhumb_azimuth_with / rhumb_destination / rhumb_destination_with / rhumb_distance / rhumb_distance_with / rhumb_length / rhumb_length_with |
✅ | → |
simplify / simplify_with |
✅ | → |
transform |
✅ | → |
triangulate_earcut |
✅ | → |
| Inspection — Query a geometry's shape or membership | ||
for_each_point / for_each_segment |
✅ | → |
is_convex |
✅ | → |
is_empty |
✅ | → |
is_simple |
✅ | → |
is_valid / is_valid_polygon / is_valid_polygon_with / is_valid_ring / is_valid_ring_with / is_valid_with / validity_reason / validity_reason_with |
✅ | → |
num_geometries |
✅ | → |
num_interior_rings |
✅ | → |
num_points |
✅ | → |
num_segments |
✅ | → |
| Mutation & assembly — Build up or normalise a geometry in place | ||
append / append_to_ring |
✅ | → |
assign_values |
✅ | → |
clear |
✅ | → |
convert |
✅ | → |
correct / correct_closure |
✅ | → |
make_box / make_point / make_segment |
✅ | → |
merge_elements / merge_multipolygon / merge_polygons / stitch_triangles |
✅ | → |
remove_spikes |
✅ | → |
reverse |
✅ | → |
unique |
✅ | → |
| Spatial index — Bulk-loadable R-tree with nearest-neighbour and predicate queries | ||
and / not / satisfies |
✅ | → |
Rtree |
✅ | → |
| I/O — Well-Known Text — Parse and write the OGC WKT format | ||
from_wkt / parse_linestring / parse_multi_linestring / parse_multi_point / parse_multi_polygon / parse_point / parse_polygon |
❌ | → |
to_wkt / to_wkt_polygon / write_wkt |
❌ | → |
| I/O — Well-Known Binary — Parse and write the OGC WKB format | ||
from_wkb |
❌ | → |
to_wkb / to_wkb_polygon |
❌ | → |
| I/O — GeoJSON — Parse and write GeoJSON (RFC 7946) | ||
from_geojson |
❌ | → |
to_geojson / to_geojson_polygon |
❌ | → |
| I/O — SVG — Render geometries to SVG (debugging) | ||
SvgMapper |
❌ | → |
| Reprojection — CRS-to-CRS point reprojection (standalone crate) | ||
reproject |
✅ | → |
Ecosystem adapters register the types of other crates so the algorithms
above accept them directly:
geo-types (GeoPoint,
GeoPolygon, …) and nalgebra
(NaPoint2, NaVector3, …). Both are no_std.
Everything the boost_geometry facade re-exports is browsable from one
place: docs.rs/boost_geometry. The I/O,
projection, and adapter crates are separate dependencies — their columns link
to their own docs.rs pages.
Nineteen crates form a dependency spine from foundational tag/coords
crates up through traits, models, strategies, and algorithms to the
boost_geometry facade — plus adapters (nalgebra, geo-types), IO
(WKT, WKB, GeoJSON, SVG), an R-tree, overlay operations, and
projections. boost_geometry re-exports everything; depend on it alone
unless you need a slimmer build.
See docs/01-architecture.md
for the full map, and the per-crate no_std status just below.
Generated by .github/scripts/no_std_support.py, which builds every crate
with --no-default-features (falling back to --features libm for crates
that need a libm-backed Float impl) and is checked in CI:
| Crate | no_std |
|---|---|
boost_geometry |
✅ |
geometry-adapt |
✅ |
geometry-adapt-geo-types |
✅ |
geometry-adapt-nalgebra |
✅ |
geometry-algorithm |
✅ |
geometry-coords |
✅ |
geometry-cs |
✅ |
geometry-derive |
✅ |
geometry-io-geojson |
❌ |
geometry-io-svg |
❌ |
geometry-io-wkb |
❌ |
geometry-io-wkt |
❌ |
geometry-model |
✅ |
geometry-overlay |
✅ |
geometry-proj |
✅ |
geometry-rtree |
✅ |
geometry-strategy |
✅ |
geometry-tag |
✅ |
geometry-trait |
✅ |
BSL-1.0 — see LICENSE.