packed_spatial_index
A packed static spatial index for 2D and 3D axis-aligned bounding boxes
(AABBs), in the style of flatbush /
static_aabb2d_index. Pack the
boxes into a Hilbert R-tree once, then run many queries over it with SIMD:
- range / intersection search
- nearest neighbors (kNN) from a point or a box
- ray casts (all hits or the closest)
- spatial joins between two indexes
Builds and SIMD searches run faster than comparable Rust indexes (benchmarks), and the same bytes load back as zero-copy, mmap-friendly views. A file can also carry an optional per-item payload and file-level metadata, and a streaming reader can query a large or remote index without loading the whole file.
use ;
let mut builder = new;
builder.add;
builder.add;
let index = builder.finish?;
let hits = index.search;
assert_eq!;
# Ok::
Installation
Requires Rust 1.89 or newer.
[]
= "0.7"
When to use it
Use this crate when your geometry is static or rebuilt in batches, you can key
results by insertion-order index into your own payload array, and you want a
compact in-memory (or mmap'd) index with reusable buffers for high query
throughput. It is not a dynamic R-tree — there are no insert/delete
operations after finish().
Performance
Built for throughput on static geometry: fast builds, SIMD range / kNN / raycast
(AVX2 / AVX-512), and reusable query buffers for tight loops. The
Performance page has the full benchmarks against
static_aabb2d_index, FlatGeobuf and the bvh crate, showing where it leads and
where it doesn't.
Queries at a glance
Every query exists on Index2D / Index3D, the simd-feature SimdIndex2D /
SimdIndex3D, and the zero-copy views. Range/ray results are item indices in
insertion order; result order is unspecified. See
docs.rs for full per-method docs.
| Query | Methods |
|---|---|
| Range / intersection | search, search_iter, search_into, search_with, any, first, visit |
| Nearest neighbors (point) | neighbors, neighbors_within, neighbors_into, neighbors_with, visit_neighbors |
| Nearest neighbors (box) | neighbors_of_box, neighbors_of_box_within, neighbors_of_box_into, neighbors_of_box_with, visit_neighbors_of_box |
| Ray segment | raycast, raycast_into, raycast_with, raycast_closest, raycast_closest_with, visit_raycast |
| Spatial join | join, join_with, self_join, self_join_with |
| Extent / exact | extent, and search_exact / neighbors_exact on the f32 indexes |
# use ;
# let mut b = new;
# b.add;
# b.add;
# let index = b.finish?;
let overlaps = index.search; // range query
let nearest = index.neighbors; // kNN
let hit = index.raycast_closest;
assert_eq!;
assert_eq!;
assert_eq!;
# Ok::
Types at a glance
- Geometry:
Box2D,Box3D(inclusiveoverlaps/contains/contains_point/from_point),Point2D,Point3D,Ray2D,Ray3D. - Builders:
Index2DBuilder,Index3DBuilder—finish(scalar),finish_simd(SoA + SIMD),finish_simd_f32(compact f32 boxes). - Indexes:
Index2D/Index3D(scalar),SimdIndex2D/SimdIndex3D(SIMD),SimdIndex2DF32/SimdIndex3DF32(half-memory f32 boxes). - Views: zero-copy
Index2DView/Index3DView(and SIMD / f32 view variants) over serialized bytes. - Workspaces:
SearchWorkspace/NeighborWorkspacereuse buffers in loops. - Sorting / errors:
SortKey2D/SortKey3D(defaultHilbert),BoundsError,BuildError,LoadError.
Serialization & metadata
to_bytes / from_bytes round-trip an index; the serialize() builder adds the
optional pieces — one opaque payload blob per item and descriptive metadata
(coordinate reference system, payload content type, attribution):
# use ;
# let mut b = new;
# b.add;
# let index = b.finish?;
let bytes = index
.serialize
.crs
.payloads
.to_bytes?;
// Read the metadata back without loading the index.
assert_eq!;
# Ok::
The metadata is opaque (the crate stores the strings you give it, verbatim). Pair query results with their payloads via the zero-copy views or the streaming reader — see Persistence and the binary format.
Features
| Feature | Pulls in | Adds |
|---|---|---|
parallel (default) |
rayon |
adaptive parallel index builds |
simd (default) |
wide |
SoA indexes + SIMD search / raycast (AVX2 / AVX-512) |
f32-storage |
(implies simd) |
compact f32-storage SIMD indexes |
stream |
— | query a serialized index over a RangeReader (local file or remote object) without loading it whole |
async |
futures-util (implies stream) |
query over an AsyncRangeReader (browser / edge worker, HTTP range or object storage) |
bench-internals |
— | hidden support API for this crate's benchmarks |
Serialization, metadata, and the scalar indexes are always available — no feature required.
Documentation
- Guide — recipes, choosing a query method, builder configuration, examples, WASM demo.
- Persistence — serialize / load / zero-copy views, querying large or on-disk indexes via mmap, and streaming queries over a
RangeReader(local file or remote object). - Performance — benchmarks vs
static_aabb2d_index, FlatGeobuf, and thebvhcrate. - Binary format — the
PSINDEXon-disk layout. - API reference — docs.rs/packed_spatial_index.
Limitations
- Static: rebuild when the dataset changes; no insert/delete.
- Results are item indices, not stored payloads; result order is unspecified.
f32-storageindexes store outward-rounded boxes — plain range search may return extra near-boundary hits; usesearch_exact/neighbors_exact(with your sourcef64boxes) for exact results, and preferf64indexes for exact queries with many hits.
Safety
The public API is safe Rust; unsafe is confined to narrow, audited paths
(validated unaligned reads, repr(C) bulk copies, gated x86-64 SIMD). Serialized
input is treated as untrusted: the in-memory loaders validate the whole buffer
before use, and the streaming reader validates pointers and payload offsets as it
follows them, with per-query cost limits to bound broad queries. See
SAFETY.md for the memory-safety and untrusted-input hardening
details.
Status
Pre-1.0: the API and on-disk format may still change between minor releases.
The crate is covered by unit, property and fuzz tests across the feature matrix,
but it has not yet been proven in production. Validate it for your workload
before you depend on it.
Feedback
Built something with it? I'd love to hear about it! Start a
discussion with your
use case, your numbers or any rough edges, and file an
issue for bugs.
Real-world reports are what push it toward 1.0.
AI usage note
AI assistance is part of my development process for this project. I guide the architecture, review generated output carefully, and take responsibility for the crate as published.
License
Licensed under the Apache License, Version 2.0.