Skip to main content

packed_spatial_index/
index2d.rs

1//! Static spatial index implementation for 2D AABBs:
2//! a packed Hilbert R-tree in the style of flatbush / `static_aabb2d_index`.
3//!
4//! The public API is intentionally small: add boxes with [`crate::Index2DBuilder`],
5//! call [`crate::Index2DBuilder::finish`], then search the finished [`Index2D`].
6//!
7//! # Example
8//! ```
9//! use packed_spatial_index::{Index2DBuilder, Box2D};
10//!
11//! let mut builder = Index2DBuilder::new(3);
12//! builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
13//! builder.add(Box2D::new(5.0, 5.0, 6.0, 6.0));
14//! builder.add(Box2D::new(0.5, 0.5, 2.0, 2.0));
15//! let index = builder.finish().unwrap();
16//!
17//! let hits = index.search(Box2D::new(0.0, 0.0, 1.5, 1.5));
18//! assert!(hits.contains(&0) && hits.contains(&2));
19//! assert!(!hits.contains(&1));
20//! ```
21
22use std::{collections::BinaryHeap, ops::ControlFlow};
23
24use crate::config::{DEFAULT_NEIGHBOR_QUEUE_CAPACITY, DEFAULT_SEARCH_STACK_CAPACITY};
25use crate::geometry::{Box2D, Point2D};
26use crate::join::{join_core, self_join_core};
27use crate::neighbors::{
28    NeighborNodeState, NeighborQuery2D, NeighborState, NeighborWorkspace, best_first, metric_knn,
29};
30use crate::persistence::{
31    LoadError, ParsedPayload, PayloadError, build_id_to_leaf, parse_index, payload_slice,
32    read_f64_le_unchecked, read_u64_le_unchecked,
33};
34use crate::range::{collect_overlaps, visit_overlaps};
35use crate::traversal::{SearchWorkspace, prefetch_read, upper_bound_level};
36use crate::tree_access::{TreeAccess, leaf_group_range};
37use crate::triangle::{Triangle2, blobs_as_records};
38
39mod raycast;
40mod region;
41mod serializer;
42pub use serializer::Serializer2D;
43
44#[inline]
45fn prefetch_aos_node(entries: &[Box2D], indices: &[usize], node_index: usize, node_size: usize) {
46    if node_index < entries.len() {
47        prefetch_read(entries.as_ptr().wrapping_add(node_index));
48        prefetch_read(indices.as_ptr().wrapping_add(node_index));
49    }
50    let next_line = node_index.saturating_add((64 / std::mem::size_of::<Box2D>()).max(1));
51    if node_size > 1 && next_line < entries.len() {
52        prefetch_read(entries.as_ptr().wrapping_add(next_line));
53        prefetch_read(indices.as_ptr().wrapping_add(next_line));
54    }
55}
56
57/// Finished static read-only index.
58///
59/// Search methods return item positions in the original insertion order. The order
60/// of returned results is traversal order and is not part of the API.
61///
62/// # Example
63///
64/// ```
65/// use packed_spatial_index::{Index2DBuilder, Box2D};
66///
67/// let mut builder = Index2DBuilder::new(2);
68/// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
69/// builder.add(Box2D::new(5.0, 5.0, 6.0, 6.0));
70/// let index = builder.finish().unwrap();
71///
72/// assert_eq!(index.num_items(), 2);
73/// assert_eq!(index.search(Box2D::new(0.0, 0.0, 2.0, 2.0)), vec![0]);
74/// ```
75pub struct Index2D {
76    pub(crate) node_size: usize,
77    pub(crate) num_items: usize,
78    pub(crate) level_bounds: Vec<usize>,
79    pub(crate) entries: Vec<Box2D>,
80    pub(crate) indices: Vec<usize>,
81}
82
83impl Index2D {
84    /// Return the number of indexed items.
85    pub fn num_items(&self) -> usize {
86        self.num_items
87    }
88
89    /// Return the total extent of indexed items, or `None` for an empty index.
90    pub fn extent(&self) -> Option<Box2D> {
91        self.entries.last().copied()
92    }
93
94    /// Return the packed node size used by this index.
95    pub fn node_size(&self) -> usize {
96        self.node_size
97    }
98
99    /// Serialize this index into the stable little-endian `PSINDEX` format.
100    ///
101    /// # Example
102    ///
103    /// ```
104    /// use packed_spatial_index::{Index2D, Index2DBuilder, Index2DView, Box2D};
105    ///
106    /// let mut builder = Index2DBuilder::new(1);
107    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
108    /// let index = builder.finish()?;
109    ///
110    /// let bytes = index.to_bytes();
111    /// let owned = Index2D::from_bytes(&bytes)?;
112    /// let view = Index2DView::from_bytes(&bytes)?;
113    ///
114    /// let query = Box2D::new(0.5, 0.5, 0.5, 0.5);
115    /// assert_eq!(owned.search(query), view.search(query));
116    /// # Ok::<(), Box<dyn std::error::Error>>(())
117    /// ```
118    pub fn to_bytes(&self) -> Vec<u8> {
119        let mut out = Vec::new();
120        self.to_bytes_into(&mut out);
121        out
122    }
123
124    /// Serialize into a caller-provided buffer, reusing its allocation.
125    ///
126    /// Equivalent to [`to_bytes`](Self::to_bytes) but writes into `out` (cleared
127    /// first). Reusing one buffer across many serializations avoids repeated
128    /// multi-megabyte allocation and page-faulting, which dominates the cost for large
129    /// indexes.
130    ///
131    /// # Example
132    ///
133    /// ```
134    /// use packed_spatial_index::{Index2D, Index2DBuilder, Box2D};
135    ///
136    /// let mut builder = Index2DBuilder::new(1);
137    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
138    /// let index = builder.finish()?;
139    ///
140    /// let mut buffer = Vec::new();
141    /// index.to_bytes_into(&mut buffer);
142    /// assert_eq!(buffer, index.to_bytes());
143    /// # Ok::<(), Box<dyn std::error::Error>>(())
144    /// ```
145    pub fn to_bytes_into(&self, out: &mut Vec<u8>) {
146        self.serialize()
147            .to_bytes_into(out)
148            .expect("serialization without payloads cannot fail");
149    }
150
151    /// Serialize this index together with one opaque payload per item, producing
152    /// a self-contained file (the spatial index plus the data it indexes).
153    ///
154    /// `payloads` is in item order: `payloads[i]` is the blob for the item added
155    /// `i`-th. Read them back via `StreamIndex2D::search_payloads` (`stream`
156    /// feature) or [`Index2DView::search_payloads`] / [`Index2DView::payload`].
157    /// Returns [`PayloadError::CountMismatch`] unless `payloads.len()` equals
158    /// [`num_items`](Self::num_items). Shorthand for
159    /// [`serialize().payloads(..)`](Self::serialize).
160    ///
161    /// ```
162    /// use packed_spatial_index::{Box2D, Index2DBuilder};
163    /// let mut builder = Index2DBuilder::new(2);
164    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
165    /// builder.add(Box2D::new(5.0, 5.0, 6.0, 6.0));
166    /// let index = builder.finish()?;
167    /// let bytes = index.to_bytes_with_payloads(&[b"first".as_slice(), b"second"])?;
168    /// assert!(bytes.len() > index.to_bytes().len());
169    /// # Ok::<(), Box<dyn std::error::Error>>(())
170    /// ```
171    pub fn to_bytes_with_payloads<P: AsRef<[u8]>>(
172        &self,
173        payloads: &[P],
174    ) -> Result<Vec<u8>, PayloadError> {
175        self.serialize().payloads(payloads).to_bytes()
176    }
177
178    /// [`to_bytes_with_payloads`](Self::to_bytes_with_payloads) into a reused
179    /// buffer (cleared first).
180    pub fn to_bytes_with_payloads_into<P: AsRef<[u8]>>(
181        &self,
182        payloads: &[P],
183        out: &mut Vec<u8>,
184    ) -> Result<(), PayloadError> {
185        self.serialize().payloads(payloads).to_bytes_into(out)
186    }
187
188    /// Serialize in the **interleaved** layout (each node's box followed by its
189    /// index), a streaming-tuned layout a [`StreamIndex2D`] fetches in one read
190    /// per level instead of two. The in-memory loaders and SIMD views read the
191    /// default layout only. Shorthand for
192    /// [`serialize().interleaved()`](Self::serialize); available with `stream`.
193    ///
194    /// [`StreamIndex2D`]: crate::StreamIndex2D
195    #[cfg(feature = "stream")]
196    pub fn to_bytes_interleaved(&self) -> Vec<u8> {
197        self.serialize()
198            .interleaved()
199            .to_bytes()
200            .expect("serialization without payloads cannot fail")
201    }
202
203    /// Interleaved layout plus one payload per item. Shorthand for
204    /// [`serialize().interleaved().payloads(..)`](Self::serialize); available with
205    /// `stream`.
206    #[cfg(feature = "stream")]
207    pub fn to_bytes_interleaved_with_payloads<P: AsRef<[u8]>>(
208        &self,
209        payloads: &[P],
210    ) -> Result<Vec<u8>, PayloadError> {
211        self.serialize().interleaved().payloads(payloads).to_bytes()
212    }
213
214    /// Start a serialization builder for fine-grained control: optional per-item
215    /// payloads, the streaming-tuned interleaved layout, and descriptive metadata
216    /// (CRS / content type / attribution). See [`Serializer2D`].
217    ///
218    /// ```
219    /// use packed_spatial_index::{Box2D, Index2DBuilder};
220    /// let mut builder = Index2DBuilder::new(1);
221    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
222    /// let index = builder.finish()?;
223    /// let bytes = index
224    ///     .serialize()
225    ///     .crs("EPSG:4326")
226    ///     .payloads(&[b"feature-0".as_slice()])
227    ///     .to_bytes()?;
228    /// assert_eq!(packed_spatial_index::read_metadata(&bytes)?.crs.as_deref(), Some("EPSG:4326"));
229    /// # Ok::<(), Box<dyn std::error::Error>>(())
230    /// ```
231    pub fn serialize(&self) -> Serializer2D<'_> {
232        Serializer2D::new(self)
233    }
234
235    /// Build an index over the bounding box of each triangle, in slice order
236    /// (item `i` is `triangles[i]`). A convenience over looping
237    /// [`Index2DBuilder::add`](crate::Index2DBuilder::add) with
238    /// [`Triangle2::aabb`](crate::Triangle2::aabb); the index is queryable in memory, and
239    /// `index.serialize().triangles(triangles)` stores the geometry alongside it.
240    /// Use the builder directly for custom boxes or build options like `node_size`.
241    pub fn from_triangles<T: Triangle2>(triangles: &[T]) -> Result<Self, crate::BuildError> {
242        let mut builder = crate::Index2DBuilder::new(triangles.len());
243        for t in triangles {
244            builder.add(t.aabb());
245        }
246        builder.finish()
247    }
248
249    /// Load an owned index from bytes previously produced by [`Index2D::to_bytes`].
250    pub fn from_bytes(bytes: &[u8]) -> Result<Self, LoadError> {
251        let view = Index2DView::from_bytes(bytes)?;
252
253        let mut level_bounds = Vec::with_capacity(view.level_count);
254        for i in 0..view.level_count {
255            level_bounds.push(view.level_bound_unchecked(i));
256        }
257
258        let mut entries = Vec::with_capacity(view.num_nodes);
259        for i in 0..view.num_nodes {
260            entries.push(view.entry_at_unchecked(i));
261        }
262
263        let mut indices = Vec::with_capacity(view.num_nodes);
264        for i in 0..view.num_nodes {
265            indices.push(view.index_at_unchecked(i));
266        }
267
268        Ok(Self {
269            node_size: view.node_size,
270            num_items: view.num_items,
271            level_bounds,
272            entries,
273            indices,
274        })
275    }
276
277    /// Return the indices of all items whose boxes intersect `query`.
278    ///
279    /// # Example
280    ///
281    /// ```
282    /// # use packed_spatial_index::{Index2DBuilder, Box2D};
283    /// # let mut builder = Index2DBuilder::new(2);
284    /// # builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
285    /// # builder.add(Box2D::new(5.0, 5.0, 6.0, 6.0));
286    /// # let index = builder.finish().unwrap();
287    /// assert_eq!(index.search(Box2D::new(0.0, 0.0, 2.0, 2.0)), vec![0]);
288    /// ```
289    pub fn search(&self, query: Box2D) -> Vec<usize> {
290        let mut results = Vec::new();
291        self.search_into(query, &mut results);
292        results
293    }
294
295    /// Search with a reusable result buffer.
296    pub fn search_into(&self, query: Box2D, results: &mut Vec<usize>) {
297        let mut stack: Vec<usize> = Vec::with_capacity(DEFAULT_SEARCH_STACK_CAPACITY);
298        self.search_into_stack(query, results, &mut stack);
299    }
300
301    /// Search with reusable result and traversal buffers.
302    ///
303    /// # Example
304    ///
305    /// ```
306    /// use packed_spatial_index::{Index2DBuilder, Box2D, SearchWorkspace};
307    ///
308    /// let mut builder = Index2DBuilder::new(1);
309    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
310    /// let index = builder.finish().unwrap();
311    ///
312    /// let mut workspace = SearchWorkspace::with_capacity(8, 8);
313    /// let hits = index.search_with(Box2D::new(0.0, 0.0, 2.0, 2.0), &mut workspace);
314    /// assert_eq!(hits, &[0]);
315    /// assert_eq!(workspace.results(), &[0]);
316    /// ```
317    pub fn search_with<'a>(&self, query: Box2D, workspace: &'a mut SearchWorkspace) -> &'a [usize] {
318        self.search_into_stack(query, &mut workspace.results, &mut workspace.stack);
319        &workspace.results
320    }
321
322    /// Return `true` if at least one item intersects `query`.
323    ///
324    /// This is an early-exit path: traversal stops at the first hit and does not
325    /// allocate a result `Vec`.
326    ///
327    /// # Example
328    ///
329    /// ```
330    /// # use packed_spatial_index::{Index2DBuilder, Box2D};
331    /// # let mut builder = Index2DBuilder::new(2);
332    /// # builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
333    /// # builder.add(Box2D::new(5.0, 5.0, 6.0, 6.0));
334    /// # let index = builder.finish().unwrap();
335    /// assert!(index.any(Box2D::new(0.0, 0.0, 2.0, 2.0)));
336    /// assert!(!index.any(Box2D::new(20.0, 20.0, 21.0, 21.0)));
337    /// ```
338    pub fn any(&self, query: Box2D) -> bool {
339        self.visit(query, |_| ControlFlow::Break(())).is_break()
340    }
341
342    /// Return one intersecting item, if any.
343    ///
344    /// Tree traversal order is not part of the API, so this returns just some first
345    /// found item, not the minimum insertion index.
346    ///
347    /// # Example
348    ///
349    /// ```
350    /// # use packed_spatial_index::{Index2DBuilder, Box2D};
351    /// # let mut builder = Index2DBuilder::new(2);
352    /// # builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
353    /// # builder.add(Box2D::new(5.0, 5.0, 6.0, 6.0));
354    /// # let index = builder.finish().unwrap();
355    /// assert_eq!(index.first(Box2D::new(0.0, 0.0, 2.0, 2.0)), Some(0));
356    /// assert_eq!(index.first(Box2D::new(20.0, 20.0, 21.0, 21.0)), None);
357    /// ```
358    pub fn first(&self, query: Box2D) -> Option<usize> {
359        match self.visit(query, ControlFlow::Break) {
360            ControlFlow::Break(index) => Some(index),
361            ControlFlow::Continue(()) => None,
362        }
363    }
364
365    /// Return up to `max_results` item indices nearest to `point`.
366    ///
367    /// # Example
368    ///
369    /// ```
370    /// use packed_spatial_index::{Index2DBuilder, Point2D, Box2D};
371    ///
372    /// let mut builder = Index2DBuilder::new(2);
373    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
374    /// builder.add(Box2D::new(10.0, 10.0, 11.0, 11.0));
375    /// let index = builder.finish().unwrap();
376    ///
377    /// assert_eq!(index.neighbors(Point2D::new(10.25, 10.25), 1), vec![1]);
378    /// ```
379    pub fn neighbors(&self, point: Point2D, max_results: usize) -> Vec<usize> {
380        self.neighbors_within(point, max_results, f64::INFINITY)
381    }
382
383    /// Return up to `max_results` item indices within `max_distance` of `point`.
384    pub fn neighbors_within(
385        &self,
386        point: Point2D,
387        max_results: usize,
388        max_distance: f64,
389    ) -> Vec<usize> {
390        let mut results = Vec::new();
391        self.neighbors_into(point, max_results, max_distance, &mut results);
392        results
393    }
394
395    /// Nearest-neighbor search with a reusable result buffer.
396    pub fn neighbors_into(
397        &self,
398        point: Point2D,
399        max_results: usize,
400        max_distance: f64,
401        results: &mut Vec<usize>,
402    ) {
403        results.clear();
404        if max_results == 0 {
405            return;
406        }
407        if max_results == 1 {
408            let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
409            if let Some(index) =
410                self.nearest_one_with_queue(NeighborQuery2D::Point(point), max_distance, &mut queue)
411            {
412                results.push(index);
413            }
414            return;
415        }
416
417        let mut item_queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
418        let mut node_queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
419        self.collect_neighbors_with_queues(
420            NeighborQuery2D::Point(point),
421            max_results,
422            max_distance,
423            results,
424            &mut item_queue,
425            &mut node_queue,
426        );
427    }
428
429    /// Nearest-neighbor search with reusable result and priority-queue buffers.
430    pub fn neighbors_with<'a>(
431        &self,
432        point: Point2D,
433        max_results: usize,
434        max_distance: f64,
435        workspace: &'a mut NeighborWorkspace,
436    ) -> &'a [usize] {
437        workspace.results.clear();
438        if max_results == 0 {
439            workspace.queue.clear();
440            workspace.node_queue.clear();
441            return &workspace.results;
442        }
443        if max_results == 1 {
444            workspace.queue.clear();
445            if let Some(index) = self.nearest_one_with_queue(
446                NeighborQuery2D::Point(point),
447                max_distance,
448                &mut workspace.node_queue,
449            ) {
450                workspace.results.push(index);
451            }
452            return &workspace.results;
453        }
454
455        self.collect_neighbors_with_queues(
456            NeighborQuery2D::Point(point),
457            max_results,
458            max_distance,
459            &mut workspace.results,
460            &mut workspace.queue,
461            &mut workspace.node_queue,
462        );
463        &workspace.results
464    }
465
466    /// Visit items in nondecreasing squared-distance order from `point`.
467    ///
468    /// The visitor receives squared distances. Return [`ControlFlow::Break`] to
469    /// stop early.
470    pub fn visit_neighbors<B, F>(
471        &self,
472        point: Point2D,
473        max_distance: f64,
474        mut visitor: F,
475    ) -> ControlFlow<B>
476    where
477        F: FnMut(usize, f64) -> ControlFlow<B>,
478    {
479        let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
480        self.visit_neighbors_with_queue(
481            NeighborQuery2D::Point(point),
482            max_distance,
483            &mut queue,
484            &mut visitor,
485        )
486    }
487
488    /// Up to `max_results` item indices nearest to your query under a custom
489    /// distance `metric`, nearest first.
490    ///
491    /// `metric(b)` returns the distance from your query to box `b`; the query
492    /// point and any parameters are captured by the closure. It must be an
493    /// **admissible lower bound** — the distance to a box never exceeds the
494    /// distance to any item inside it, which holds for any "distance to the
495    /// closest point of the box" metric (a child box is contained in its parent).
496    /// `max_distance` is a cutoff in the metric's own units (not squared);
497    /// `f64::INFINITY` for unbounded. Results are ordered by the metric distance.
498    ///
499    /// The default [`neighbors`](Self::neighbors) (squared Euclidean) is faster;
500    /// reach for this when you need another metric, e.g. great-circle distance via
501    /// [`haversine_distance_2d`](crate::haversine_distance_2d).
502    ///
503    /// # Example
504    ///
505    /// ```
506    /// use packed_spatial_index::{Index2DBuilder, Box2D, Point2D, haversine_distance_2d, EARTH_RADIUS_M};
507    ///
508    /// // Two lon/lat points; query near the first.
509    /// let mut b = Index2DBuilder::new(2);
510    /// b.add(Box2D::from_point(Point2D::new(13.40, 52.52))); // Berlin
511    /// b.add(Box2D::from_point(Point2D::new(2.35, 48.86)));  // Paris
512    /// let index = b.finish().unwrap();
513    ///
514    /// let q = (13.0, 52.4);
515    /// let hits = index.neighbors_metric(
516    ///     |bx| haversine_distance_2d(q, bx, EARTH_RADIUS_M),
517    ///     1,
518    ///     f64::INFINITY,
519    /// );
520    /// assert_eq!(hits, vec![0]); // Berlin
521    /// ```
522    pub fn neighbors_metric<M: Fn(Box2D) -> f64>(
523        &self,
524        metric: M,
525        max_results: usize,
526        max_distance: f64,
527    ) -> Vec<usize> {
528        let mut results = Vec::new();
529        self.neighbors_metric_into(metric, max_results, max_distance, &mut results);
530        results
531    }
532
533    /// [`neighbors_metric`](Self::neighbors_metric) into a reused buffer (cleared first).
534    pub fn neighbors_metric_into<M: Fn(Box2D) -> f64>(
535        &self,
536        metric: M,
537        max_results: usize,
538        max_distance: f64,
539        results: &mut Vec<usize>,
540    ) {
541        results.clear();
542        let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
543        metric_knn::collect_neighbors(
544            self.entries.len(),
545            self.num_items,
546            self.node_size,
547            |node| self.level_bounds[upper_bound_level(&self.level_bounds, node)],
548            |pos| self.indices[pos],
549            max_results,
550            max_distance,
551            |pos| metric(self.entries[pos]),
552            results,
553            &mut queue,
554        );
555    }
556
557    /// Visit items in nondecreasing custom-`metric` distance; the visitor receives
558    /// the metric distance and may return [`ControlFlow::Break`] to stop early.
559    /// See [`neighbors_metric`](Self::neighbors_metric) for the metric contract.
560    pub fn visit_neighbors_metric<B, M, F>(
561        &self,
562        metric: M,
563        max_distance: f64,
564        mut visitor: F,
565    ) -> ControlFlow<B>
566    where
567        M: Fn(Box2D) -> f64,
568        F: FnMut(usize, f64) -> ControlFlow<B>,
569    {
570        let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
571        metric_knn::visit_neighbors(
572            self.entries.len(),
573            self.num_items,
574            self.node_size,
575            |node| self.level_bounds[upper_bound_level(&self.level_bounds, node)],
576            |pos| self.indices[pos],
577            max_distance,
578            |pos| metric(self.entries[pos]),
579            &mut queue,
580            &mut visitor,
581        )
582    }
583
584    /// Return up to `max_results` item indices nearest to the box `query`.
585    ///
586    /// Distance is the box-to-box gap: items overlapping or touching `query`
587    /// have distance `0.0` and come first (their mutual order is unspecified).
588    ///
589    /// # Example
590    ///
591    /// ```
592    /// use packed_spatial_index::{Index2DBuilder, Box2D};
593    ///
594    /// let mut builder = Index2DBuilder::new(2);
595    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
596    /// builder.add(Box2D::new(10.0, 0.0, 11.0, 1.0));
597    /// let index = builder.finish().unwrap();
598    ///
599    /// // The query box's nearest edge is closer to item 1 than to item 0.
600    /// let query = Box2D::new(7.0, 0.0, 8.0, 1.0);
601    /// assert_eq!(index.neighbors_of_box(query, 1), vec![1]);
602    /// ```
603    pub fn neighbors_of_box(&self, query: Box2D, max_results: usize) -> Vec<usize> {
604        self.neighbors_of_box_within(query, max_results, f64::INFINITY)
605    }
606
607    /// Return up to `max_results` item indices within `max_distance` of the
608    /// box `query`. See [`neighbors_of_box`](Self::neighbors_of_box).
609    pub fn neighbors_of_box_within(
610        &self,
611        query: Box2D,
612        max_results: usize,
613        max_distance: f64,
614    ) -> Vec<usize> {
615        let mut results = Vec::new();
616        self.neighbors_of_box_into(query, max_results, max_distance, &mut results);
617        results
618    }
619
620    /// Box-query nearest-neighbor search with a reusable result buffer.
621    pub fn neighbors_of_box_into(
622        &self,
623        query: Box2D,
624        max_results: usize,
625        max_distance: f64,
626        results: &mut Vec<usize>,
627    ) {
628        results.clear();
629        if max_results == 0 {
630            return;
631        }
632        if max_results == 1 {
633            let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
634            if let Some(index) =
635                self.nearest_one_with_queue(NeighborQuery2D::Box(query), max_distance, &mut queue)
636            {
637                results.push(index);
638            }
639            return;
640        }
641
642        let mut item_queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
643        let mut node_queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
644        self.collect_neighbors_with_queues(
645            NeighborQuery2D::Box(query),
646            max_results,
647            max_distance,
648            results,
649            &mut item_queue,
650            &mut node_queue,
651        );
652    }
653
654    /// Box-query nearest-neighbor search with reusable result and
655    /// priority-queue buffers.
656    pub fn neighbors_of_box_with<'na>(
657        &self,
658        query: Box2D,
659        max_results: usize,
660        max_distance: f64,
661        workspace: &'na mut NeighborWorkspace,
662    ) -> &'na [usize] {
663        workspace.results.clear();
664        if max_results == 0 {
665            workspace.queue.clear();
666            workspace.node_queue.clear();
667            return &workspace.results;
668        }
669        if max_results == 1 {
670            workspace.queue.clear();
671            if let Some(index) = self.nearest_one_with_queue(
672                NeighborQuery2D::Box(query),
673                max_distance,
674                &mut workspace.node_queue,
675            ) {
676                workspace.results.push(index);
677            }
678            return &workspace.results;
679        }
680
681        self.collect_neighbors_with_queues(
682            NeighborQuery2D::Box(query),
683            max_results,
684            max_distance,
685            &mut workspace.results,
686            &mut workspace.queue,
687            &mut workspace.node_queue,
688        );
689        &workspace.results
690    }
691
692    /// Visit items in nondecreasing box-to-box distance order from `query`.
693    ///
694    /// The visitor receives squared gap distances (`0.0` for items overlapping
695    /// the query box). Return [`ControlFlow::Break`] to stop early.
696    pub fn visit_neighbors_of_box<B, F>(
697        &self,
698        query: Box2D,
699        max_distance: f64,
700        mut visitor: F,
701    ) -> ControlFlow<B>
702    where
703        F: FnMut(usize, f64) -> ControlFlow<B>,
704    {
705        let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
706        self.visit_neighbors_with_queue(
707            NeighborQuery2D::Box(query),
708            max_distance,
709            &mut queue,
710            &mut visitor,
711        )
712    }
713
714    /// Visit intersecting items without collecting a result `Vec`.
715    ///
716    /// The visitor receives item positions in the original insertion order. Return
717    /// [`ControlFlow::Continue`] to continue traversal or [`ControlFlow::Break`] for
718    /// early exit with a user-provided value.
719    ///
720    /// # Example
721    ///
722    /// ```
723    /// use std::ops::ControlFlow;
724    ///
725    /// use packed_spatial_index::{Index2DBuilder, Box2D};
726    ///
727    /// let mut builder = Index2DBuilder::new(2);
728    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
729    /// builder.add(Box2D::new(5.0, 5.0, 6.0, 6.0));
730    /// let index = builder.finish().unwrap();
731    ///
732    /// let found = index.visit(Box2D::new(5.0, 5.0, 6.0, 6.0), ControlFlow::Break);
733    /// assert_eq!(found, ControlFlow::Break(1));
734    /// ```
735    pub fn visit<B, F>(&self, query: Box2D, visitor: F) -> ControlFlow<B>
736    where
737        F: FnMut(usize) -> ControlFlow<B>,
738    {
739        let mut stack: Vec<usize> = Vec::with_capacity(DEFAULT_SEARCH_STACK_CAPACITY);
740        self.visit_with_stack(query, &mut stack, visitor)
741    }
742
743    /// Return a lazy iterator over the items intersecting `query`.
744    ///
745    /// The tree is descended on demand, so consuming only a prefix
746    /// (`.next()`, `.take(k)`, `.find(..)`) stops the traversal early and never
747    /// allocates a result `Vec`. Yielded values are original insertion indices,
748    /// in tree-traversal order (not part of the API). For the whole result set
749    /// [`search`](Index2D::search) is more direct; reach for the iterator to
750    /// compose with adapters or to bail out partway.
751    ///
752    /// # Example
753    ///
754    /// ```
755    /// use packed_spatial_index::{Index2DBuilder, Box2D};
756    ///
757    /// let mut builder = Index2DBuilder::new(3);
758    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
759    /// builder.add(Box2D::new(2.0, 2.0, 3.0, 3.0));
760    /// builder.add(Box2D::new(9.0, 9.0, 10.0, 10.0));
761    /// let index = builder.finish().unwrap();
762    ///
763    /// let mut hits: Vec<_> = index.search_iter(Box2D::new(0.0, 0.0, 4.0, 4.0)).collect();
764    /// hits.sort_unstable();
765    /// assert_eq!(hits, vec![0, 1]);
766    /// ```
767    pub fn search_iter(&self, query: Box2D) -> Search2DIter<'_> {
768        Search2DIter::new(self, query)
769    }
770
771    /// Return every pair `(i, j)` where item `i` of `self` intersects item `j`
772    /// of `other`.
773    ///
774    /// A single synchronized descent over both trees replaces one full search
775    /// per item, so large joins run far faster than a search loop. Pair order
776    /// is traversal order and is not part of the API.
777    ///
778    /// # Example
779    ///
780    /// ```
781    /// use packed_spatial_index::{Index2DBuilder, Box2D};
782    ///
783    /// let mut a = Index2DBuilder::new(2);
784    /// a.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
785    /// a.add(Box2D::new(5.0, 5.0, 6.0, 6.0));
786    /// let a = a.finish().unwrap();
787    ///
788    /// let mut b = Index2DBuilder::new(1);
789    /// b.add(Box2D::new(0.5, 0.5, 5.5, 5.5));
790    /// let b = b.finish().unwrap();
791    ///
792    /// let mut pairs = a.join(&b);
793    /// pairs.sort_unstable();
794    /// assert_eq!(pairs, vec![(0, 0), (1, 0)]);
795    /// ```
796    pub fn join(&self, other: &Index2D) -> Vec<(usize, usize)> {
797        let mut out = Vec::new();
798        let _: ControlFlow<()> = self.join_with(other, |i, j| {
799            out.push((i, j));
800            ControlFlow::Continue(())
801        });
802        out
803    }
804
805    /// Visit every intersecting pair between `self` and `other` without
806    /// collecting a result `Vec`.
807    ///
808    /// The visitor receives `(item_in_self, item_in_other)` positions in the
809    /// original insertion order of each index. Return [`ControlFlow::Break`]
810    /// for early exit.
811    pub fn join_with<B, F>(&self, other: &Index2D, visitor: F) -> ControlFlow<B>
812    where
813        F: FnMut(usize, usize) -> ControlFlow<B>,
814    {
815        join_core(self, other, visitor)
816    }
817
818    /// Return every unordered pair of distinct intersecting items within this
819    /// index, each pair exactly once.
820    ///
821    /// This is the broad-phase primitive: pairs of items whose boxes overlap.
822    /// The order of ids within a pair and the pair order are traversal order
823    /// and are not part of the API.
824    ///
825    /// # Example
826    ///
827    /// ```
828    /// use packed_spatial_index::{Index2DBuilder, Box2D};
829    ///
830    /// let mut builder = Index2DBuilder::new(3);
831    /// builder.add(Box2D::new(0.0, 0.0, 2.0, 2.0));
832    /// builder.add(Box2D::new(1.0, 1.0, 3.0, 3.0));
833    /// builder.add(Box2D::new(9.0, 9.0, 10.0, 10.0));
834    /// let index = builder.finish().unwrap();
835    ///
836    /// let pairs: Vec<_> = index
837    ///     .self_join()
838    ///     .into_iter()
839    ///     .map(|(i, j)| (i.min(j), i.max(j)))
840    ///     .collect();
841    /// assert_eq!(pairs, vec![(0, 1)]);
842    /// ```
843    pub fn self_join(&self) -> Vec<(usize, usize)> {
844        let mut out = Vec::new();
845        let _: ControlFlow<()> = self.self_join_with(|i, j| {
846            out.push((i, j));
847            ControlFlow::Continue(())
848        });
849        out
850    }
851
852    /// Visit every unordered pair of distinct intersecting items within this
853    /// index without collecting a result `Vec`.
854    ///
855    /// Return [`ControlFlow::Break`] for early exit.
856    pub fn self_join_with<B, F>(&self, visitor: F) -> ControlFlow<B>
857    where
858        F: FnMut(usize, usize) -> ControlFlow<B>,
859    {
860        self_join_core(self, visitor)
861    }
862
863    fn collect_neighbors_with_queues(
864        &self,
865        query: NeighborQuery2D,
866        max_results: usize,
867        max_distance: f64,
868        results: &mut Vec<usize>,
869        item_queue: &mut BinaryHeap<NeighborState>,
870        node_queue: &mut BinaryHeap<NeighborNodeState>,
871    ) {
872        best_first::collect_neighbors_two_queue(
873            self.entries.len(),
874            self.num_items,
875            self.node_size,
876            |n| self.level_bounds[upper_bound_level(&self.level_bounds, n)],
877            |pos| self.indices[pos],
878            max_results,
879            max_distance,
880            |pos| query.distance_squared_to(self.entries[pos]),
881            results,
882            item_queue,
883            node_queue,
884        );
885    }
886
887    fn nearest_one_with_queue(
888        &self,
889        query: NeighborQuery2D,
890        max_distance: f64,
891        queue: &mut BinaryHeap<NeighborNodeState>,
892    ) -> Option<usize> {
893        best_first::nearest_one(
894            self.entries.len(),
895            self.num_items,
896            self.node_size,
897            |n| self.level_bounds[upper_bound_level(&self.level_bounds, n)],
898            |pos| self.indices[pos],
899            max_distance,
900            |pos| query.distance_squared_to(self.entries[pos]),
901            queue,
902        )
903    }
904
905    fn visit_neighbors_with_queue<B, F>(
906        &self,
907        query: NeighborQuery2D,
908        max_distance: f64,
909        queue: &mut BinaryHeap<NeighborState>,
910        visitor: &mut F,
911    ) -> ControlFlow<B>
912    where
913        F: FnMut(usize, f64) -> ControlFlow<B>,
914    {
915        best_first::visit_neighbors(
916            self.entries.len(),
917            self.num_items,
918            self.node_size,
919            |n| self.level_bounds[upper_bound_level(&self.level_bounds, n)],
920            |pos| self.indices[pos],
921            max_distance,
922            |pos| query.distance_squared_to(self.entries[pos]),
923            queue,
924            visitor,
925        )
926    }
927
928    /// Same as [`visit`](Index2D::visit), but the traversal stack is reused by the caller.
929    #[doc(hidden)]
930    pub fn visit_with_stack<B, F>(
931        &self,
932        query: Box2D,
933        stack: &mut Vec<usize>,
934        visitor: F,
935    ) -> ControlFlow<B>
936    where
937        F: FnMut(usize) -> ControlFlow<B>,
938    {
939        // Local slice-based traversal (not the shared `visit_overlaps`): iterating
940        // `&entries[node..end]` lets LLVM autovectorize the overlap test, which a
941        // per-element `TreeAccess` kernel cannot. Measured ~1.5x faster than the
942        // generic kernel on owned visit, so kept specialized (views, whose byte
943        // storage has no slice to vectorize, keep using `visit_overlaps`).
944        self.visit_with_stack_impl::<false, B, F>(query, stack, visitor)
945    }
946
947    /// Hidden prefetch variant of [`visit_with_stack`](Index2D::visit_with_stack).
948    #[doc(hidden)]
949    pub fn visit_with_stack_prefetch<B, F>(
950        &self,
951        query: Box2D,
952        stack: &mut Vec<usize>,
953        visitor: F,
954    ) -> ControlFlow<B>
955    where
956        F: FnMut(usize) -> ControlFlow<B>,
957    {
958        self.visit_with_stack_impl::<true, B, F>(query, stack, visitor)
959    }
960
961    /// Hottest path: both result buffer and traversal stack are reused by the caller.
962    #[doc(hidden)]
963    pub fn search_into_stack(
964        &self,
965        query: Box2D,
966        results: &mut Vec<usize>,
967        stack: &mut Vec<usize>,
968    ) {
969        self.search_into_stack_contained_impl(query, results, stack);
970    }
971
972    /// Traversal variant that prefetches the next node from the stack.
973    #[doc(hidden)]
974    pub fn search_into_stack_prefetch(
975        &self,
976        query: Box2D,
977        results: &mut Vec<usize>,
978        stack: &mut Vec<usize>,
979    ) {
980        results.clear();
981        if self.num_items == 0 {
982            stack.clear();
983            return;
984        }
985
986        let root = self.entries[self.entries.len() - 1];
987        if query.contains(root) {
988            stack.clear();
989            results.extend_from_slice(&self.indices[..self.num_items]);
990            return;
991        }
992
993        self.search_into_stack_overlaps_impl::<true>(query, results, stack);
994    }
995
996    fn search_into_stack_overlaps_impl<const PREFETCH: bool>(
997        &self,
998        query: Box2D,
999        results: &mut Vec<usize>,
1000        stack: &mut Vec<usize>,
1001    ) {
1002        results.clear();
1003        stack.clear();
1004        if self.num_items == 0 {
1005            return;
1006        }
1007
1008        let mut node_index = self.entries.len() - 1;
1009        let mut level = self.level_bounds.len() - 1;
1010
1011        loop {
1012            let end = (node_index + self.node_size).min(self.level_bounds[level]);
1013            let is_leaf = node_index < self.num_items;
1014            let node_entries = &self.entries[node_index..end];
1015            let node_indices = &self.indices[node_index..end];
1016
1017            if is_leaf {
1018                for (b, &index) in node_entries.iter().zip(node_indices) {
1019                    if !b.overlaps(query) {
1020                        continue;
1021                    }
1022                    results.push(index);
1023                }
1024            } else {
1025                let child_level = level - 1;
1026                for (b, &index) in node_entries.iter().zip(node_indices).rev() {
1027                    if !b.overlaps(query) {
1028                        continue;
1029                    }
1030                    stack.push(index);
1031                    stack.push(child_level);
1032                }
1033            }
1034
1035            if stack.len() > 1 {
1036                if PREFETCH {
1037                    prefetch_aos_node(
1038                        &self.entries,
1039                        &self.indices,
1040                        stack[stack.len() - 2],
1041                        self.node_size,
1042                    );
1043                }
1044                level = stack.pop().unwrap();
1045                node_index = stack.pop().unwrap();
1046            } else {
1047                return;
1048            }
1049        }
1050    }
1051
1052    fn search_into_stack_contained_impl(
1053        &self,
1054        query: Box2D,
1055        results: &mut Vec<usize>,
1056        stack: &mut Vec<usize>,
1057    ) {
1058        results.clear();
1059        stack.clear();
1060        if self.num_items == 0 {
1061            return;
1062        }
1063
1064        const CONTAINED_FLAG: usize = 1usize << (usize::BITS - 1);
1065        const LEVEL_MASK: usize = !CONTAINED_FLAG;
1066
1067        let mut node_index = self.entries.len() - 1;
1068        let mut level = self.level_bounds.len() - 1;
1069        let mut contained = false;
1070
1071        loop {
1072            let end = (node_index + self.node_size).min(self.level_bounds[level]);
1073            let is_leaf = node_index < self.num_items;
1074            let node_entries = &self.entries[node_index..end];
1075            let node_indices = &self.indices[node_index..end];
1076
1077            if contained {
1078                self.extend_contained_leaf_indices(node_index, end, level, results);
1079            } else if is_leaf {
1080                for (b, &index) in node_entries.iter().zip(node_indices) {
1081                    if !b.overlaps(query) {
1082                        continue;
1083                    }
1084                    results.push(index);
1085                }
1086            } else {
1087                let child_level = level - 1;
1088                for (b, &index) in node_entries.iter().zip(node_indices).rev() {
1089                    if !b.overlaps(query) {
1090                        continue;
1091                    }
1092                    stack.push(index);
1093                    let encoded_level = if query.contains(*b) {
1094                        child_level | CONTAINED_FLAG
1095                    } else {
1096                        child_level
1097                    };
1098                    stack.push(encoded_level);
1099                }
1100            }
1101
1102            if stack.len() > 1 {
1103                prefetch_aos_node(
1104                    &self.entries,
1105                    &self.indices,
1106                    stack[stack.len() - 2],
1107                    self.node_size,
1108                );
1109                let encoded_level = stack.pop().unwrap();
1110                level = encoded_level & LEVEL_MASK;
1111                contained = (encoded_level & CONTAINED_FLAG) != 0;
1112                node_index = stack.pop().unwrap();
1113            } else {
1114                return;
1115            }
1116        }
1117    }
1118
1119    #[inline]
1120    fn extend_contained_leaf_indices(
1121        &self,
1122        node_index: usize,
1123        end: usize,
1124        level: usize,
1125        results: &mut Vec<usize>,
1126    ) {
1127        let (start, end) = leaf_group_range(self, node_index, end, level);
1128        results.extend_from_slice(&self.indices[start..end]);
1129    }
1130
1131    fn visit_with_stack_impl<const PREFETCH: bool, B, F>(
1132        &self,
1133        query: Box2D,
1134        stack: &mut Vec<usize>,
1135        mut visitor: F,
1136    ) -> ControlFlow<B>
1137    where
1138        F: FnMut(usize) -> ControlFlow<B>,
1139    {
1140        stack.clear();
1141        if self.num_items == 0 {
1142            return ControlFlow::Continue(());
1143        }
1144
1145        let mut node_index = self.entries.len() - 1;
1146        let mut level = self.level_bounds.len() - 1;
1147
1148        loop {
1149            let end = (node_index + self.node_size).min(self.level_bounds[level]);
1150            let is_leaf = node_index < self.num_items;
1151            let node_entries = &self.entries[node_index..end];
1152            let node_indices = &self.indices[node_index..end];
1153
1154            if is_leaf {
1155                for (b, &index) in node_entries.iter().zip(node_indices) {
1156                    if !b.overlaps(query) {
1157                        continue;
1158                    }
1159                    visitor(index)?;
1160                }
1161            } else {
1162                let child_level = level - 1;
1163                for (b, &index) in node_entries.iter().zip(node_indices).rev() {
1164                    if !b.overlaps(query) {
1165                        continue;
1166                    }
1167                    stack.push(index);
1168                    stack.push(child_level);
1169                }
1170            }
1171
1172            if stack.len() > 1 {
1173                if PREFETCH {
1174                    prefetch_aos_node(
1175                        &self.entries,
1176                        &self.indices,
1177                        stack[stack.len() - 2],
1178                        self.node_size,
1179                    );
1180                }
1181                level = stack.pop().unwrap();
1182                node_index = stack.pop().unwrap();
1183            } else {
1184                return ControlFlow::Continue(());
1185            }
1186        }
1187    }
1188
1189    /// Diagnostics: returns `(result_count, intersection_check_count)`.
1190    #[doc(hidden)]
1191    pub fn search_visited(&self, query: Box2D) -> (usize, usize) {
1192        let mut results = 0usize;
1193        let mut visited = 0usize;
1194        if self.num_items == 0 {
1195            return (0, 0);
1196        }
1197
1198        let mut node_index = self.entries.len() - 1;
1199        let mut level = self.level_bounds.len() - 1;
1200        let mut stack: Vec<usize> = Vec::with_capacity(DEFAULT_SEARCH_STACK_CAPACITY);
1201
1202        loop {
1203            let end = (node_index + self.node_size).min(self.level_bounds[level]);
1204            let is_leaf = node_index < self.num_items;
1205            for pos in node_index..end {
1206                visited += 1;
1207                let b = &self.entries[pos];
1208                if !b.overlaps(query) {
1209                    continue;
1210                }
1211                let index = self.indices[pos];
1212                if is_leaf {
1213                    results += 1;
1214                } else {
1215                    stack.push(index);
1216                    stack.push(level - 1);
1217                }
1218            }
1219
1220            if stack.len() > 1 {
1221                level = stack.pop().unwrap();
1222                node_index = stack.pop().unwrap();
1223            } else {
1224                return (results, visited);
1225            }
1226        }
1227    }
1228}
1229
1230/// Zero-copy read-only view over bytes produced by [`Index2D::to_bytes`].
1231///
1232/// Loading validates the buffer but does not copy the tree into owned vectors.
1233/// Search and nearest-neighbor methods read little-endian values directly from
1234/// the borrowed byte slice.
1235///
1236/// # Example
1237///
1238/// ```
1239/// use packed_spatial_index::{Index2DBuilder, Index2DView, Box2D};
1240///
1241/// let mut builder = Index2DBuilder::new(1);
1242/// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
1243/// let bytes = builder.finish().unwrap().to_bytes();
1244///
1245/// let view = Index2DView::from_bytes(&bytes).unwrap();
1246/// assert_eq!(view.search(Box2D::new(0.0, 0.0, 2.0, 2.0)), vec![0]);
1247/// ```
1248pub struct Index2DView<'a> {
1249    node_size: usize,
1250    num_items: usize,
1251    num_nodes: usize,
1252    level_count: usize,
1253    /// Derived at load (not stored), so owned rather than borrowed.
1254    level_bounds: Vec<usize>,
1255    entries: &'a [u8],
1256    indices: &'a [u8],
1257    payload: Option<ParsedPayload<'a>>,
1258    /// `insertion id -> leaf rank`, built when a (leaf-ordered) payload is
1259    /// present, to serve random `payload(id)` lookups.
1260    id_to_leaf: Option<Vec<u32>>,
1261}
1262
1263impl<'a> Index2DView<'a> {
1264    /// Load a zero-copy index view from bytes previously produced by [`Index2D::to_bytes`].
1265    ///
1266    /// # Example
1267    ///
1268    /// ```
1269    /// use packed_spatial_index::{Index2DBuilder, Index2DView, Box2D};
1270    ///
1271    /// let mut builder = Index2DBuilder::new(1);
1272    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
1273    /// let bytes = builder.finish()?.to_bytes();
1274    ///
1275    /// let view = Index2DView::from_bytes(&bytes)?;
1276    /// assert_eq!(view.num_items(), 1);
1277    /// # Ok::<(), Box<dyn std::error::Error>>(())
1278    /// ```
1279    pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, LoadError> {
1280        let (parsed, payload) = parse_index(bytes, 2, 8)?;
1281        // The payload is leaf-ordered; build the id -> leaf-rank map so random
1282        // `payload(id)` lookups work. Only allocated when a payload is present.
1283        let id_to_leaf = payload
1284            .is_some()
1285            .then(|| build_id_to_leaf(parsed.indices, parsed.num_items));
1286        Ok(Self {
1287            node_size: parsed.node_size,
1288            num_items: parsed.num_items,
1289            num_nodes: parsed.num_nodes,
1290            level_count: parsed.level_count,
1291            level_bounds: parsed.level_bounds,
1292            entries: parsed.entries,
1293            indices: parsed.indices,
1294            payload,
1295            id_to_leaf,
1296        })
1297    }
1298
1299    /// Whether this view's bytes carry a payload section.
1300    pub fn has_payload(&self) -> bool {
1301        self.payload.is_some()
1302    }
1303
1304    /// Borrow item `id`'s payload blob (zero-copy), or `None` if the bytes have
1305    /// no payload section or `id` is out of range.
1306    pub fn payload(&self, id: usize) -> Option<&'a [u8]> {
1307        let payload = self.payload.as_ref()?;
1308        let id_to_leaf = self.id_to_leaf.as_ref()?;
1309        let leaf_rank = *id_to_leaf.get(id)? as usize;
1310        Some(payload_slice(payload, leaf_rank))
1311    }
1312
1313    /// Borrow every triangle record as a zero-copy `&[T]` (with `T` =
1314    /// [`Triangle2D`](crate::Triangle2D) / [`Triangle2DF32`](crate::Triangle2DF32)),
1315    /// in leaf (storage) order, when the payload is a fixed-width section of that
1316    /// record type and the underlying bytes are aligned (an mmap or an aligned
1317    /// buffer). Returns `None` otherwise; [`triangle`](Self::triangle) reads one
1318    /// record by item id regardless of alignment.
1319    pub fn triangles<T: Triangle2>(&self) -> Option<&'a [T]> {
1320        let payload = self.payload.as_ref()?;
1321        if payload.stride != T::STRIDE {
1322            return None;
1323        }
1324        blobs_as_records::<T>(payload.blobs)
1325    }
1326
1327    /// The triangle stored for item `id`, by value (works at any alignment).
1328    /// `None` if there is no triangle payload of the requested type, or `id` is
1329    /// out of range. The type parameter chooses the record format
1330    /// ([`Triangle2D`](crate::Triangle2D) for `f64`,
1331    /// [`Triangle2DF32`](crate::Triangle2DF32) for `f32`).
1332    pub fn triangle<T: Triangle2>(&self, id: usize) -> Option<T> {
1333        let payload = self.payload.as_ref()?;
1334        if payload.stride != T::STRIDE {
1335            return None;
1336        }
1337        let id_to_leaf = self.id_to_leaf.as_ref()?;
1338        let leaf_rank = *id_to_leaf.get(id)? as usize;
1339        Some(T::read_le(payload_slice(payload, leaf_rank)))
1340    }
1341
1342    /// Return `(item index, payload blob)` for every item intersecting `query`.
1343    ///
1344    /// The blobs are borrowed zero-copy. This is the local/in-memory counterpart
1345    /// of the streaming `search_payloads`; both pair query results with their
1346    /// stored data. Returns an empty vec if the view has no payload section.
1347    pub fn search_payloads(&self, query: Box2D) -> Vec<(usize, &'a [u8])> {
1348        let mut out = Vec::new();
1349        if self.payload.is_none() {
1350            return out;
1351        }
1352        for id in self.search(query) {
1353            if let Some(blob) = self.payload(id) {
1354                out.push((id, blob));
1355            }
1356        }
1357        out
1358    }
1359
1360    /// Return the number of indexed items.
1361    pub fn num_items(&self) -> usize {
1362        self.num_items
1363    }
1364
1365    /// Return the total extent of indexed items, or `None` for an empty view.
1366    pub fn extent(&self) -> Option<Box2D> {
1367        if self.num_items == 0 {
1368            None
1369        } else {
1370            Some(self.entry_at_unchecked(self.num_nodes - 1))
1371        }
1372    }
1373
1374    /// Return the packed node size.
1375    pub fn node_size(&self) -> usize {
1376        self.node_size
1377    }
1378
1379    /// Return the indices of all items whose boxes intersect `query`.
1380    pub fn search(&self, query: Box2D) -> Vec<usize> {
1381        let mut results = Vec::new();
1382        self.search_into(query, &mut results);
1383        results
1384    }
1385
1386    /// Search with a reusable result buffer.
1387    pub fn search_into(&self, query: Box2D, results: &mut Vec<usize>) {
1388        let mut stack: Vec<usize> = Vec::with_capacity(DEFAULT_SEARCH_STACK_CAPACITY);
1389        self.search_into_stack(query, results, &mut stack);
1390    }
1391
1392    /// Search with reusable result and traversal buffers.
1393    pub fn search_with<'b>(&self, query: Box2D, workspace: &'b mut SearchWorkspace) -> &'b [usize] {
1394        self.search_into_stack(query, &mut workspace.results, &mut workspace.stack);
1395        &workspace.results
1396    }
1397
1398    /// Return `true` if at least one item intersects `query`.
1399    pub fn any(&self, query: Box2D) -> bool {
1400        self.visit(query, |_| ControlFlow::Break(())).is_break()
1401    }
1402
1403    /// Return one intersecting item, if any.
1404    pub fn first(&self, query: Box2D) -> Option<usize> {
1405        match self.visit(query, ControlFlow::Break) {
1406            ControlFlow::Break(index) => Some(index),
1407            ControlFlow::Continue(()) => None,
1408        }
1409    }
1410
1411    /// Return up to `max_results` item indices nearest to `point`.
1412    pub fn neighbors(&self, point: Point2D, max_results: usize) -> Vec<usize> {
1413        self.neighbors_within(point, max_results, f64::INFINITY)
1414    }
1415
1416    /// Return up to `max_results` item indices within `max_distance` of `point`.
1417    pub fn neighbors_within(
1418        &self,
1419        point: Point2D,
1420        max_results: usize,
1421        max_distance: f64,
1422    ) -> Vec<usize> {
1423        let mut results = Vec::new();
1424        self.neighbors_into(point, max_results, max_distance, &mut results);
1425        results
1426    }
1427
1428    /// Nearest-neighbor search with a reusable result buffer.
1429    pub fn neighbors_into(
1430        &self,
1431        point: Point2D,
1432        max_results: usize,
1433        max_distance: f64,
1434        results: &mut Vec<usize>,
1435    ) {
1436        results.clear();
1437        if max_results == 0 {
1438            return;
1439        }
1440        if max_results == 1 {
1441            let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1442            if let Some(index) =
1443                self.nearest_one_with_queue(NeighborQuery2D::Point(point), max_distance, &mut queue)
1444            {
1445                results.push(index);
1446            }
1447            return;
1448        }
1449
1450        let mut item_queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1451        let mut node_queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1452        self.collect_neighbors_with_queues(
1453            NeighborQuery2D::Point(point),
1454            max_results,
1455            max_distance,
1456            results,
1457            &mut item_queue,
1458            &mut node_queue,
1459        );
1460    }
1461
1462    /// Nearest-neighbor search with reusable result and priority-queue buffers.
1463    pub fn neighbors_with<'b>(
1464        &self,
1465        point: Point2D,
1466        max_results: usize,
1467        max_distance: f64,
1468        workspace: &'b mut NeighborWorkspace,
1469    ) -> &'b [usize] {
1470        workspace.results.clear();
1471        if max_results == 0 {
1472            workspace.queue.clear();
1473            workspace.node_queue.clear();
1474            return &workspace.results;
1475        }
1476        if max_results == 1 {
1477            workspace.queue.clear();
1478            if let Some(index) = self.nearest_one_with_queue(
1479                NeighborQuery2D::Point(point),
1480                max_distance,
1481                &mut workspace.node_queue,
1482            ) {
1483                workspace.results.push(index);
1484            }
1485            return &workspace.results;
1486        }
1487
1488        self.collect_neighbors_with_queues(
1489            NeighborQuery2D::Point(point),
1490            max_results,
1491            max_distance,
1492            &mut workspace.results,
1493            &mut workspace.queue,
1494            &mut workspace.node_queue,
1495        );
1496        &workspace.results
1497    }
1498
1499    /// Visit items in nondecreasing squared-distance order from `point`.
1500    pub fn visit_neighbors<B, F>(
1501        &self,
1502        point: Point2D,
1503        max_distance: f64,
1504        mut visitor: F,
1505    ) -> ControlFlow<B>
1506    where
1507        F: FnMut(usize, f64) -> ControlFlow<B>,
1508    {
1509        let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1510        self.visit_neighbors_with_queue(
1511            NeighborQuery2D::Point(point),
1512            max_distance,
1513            &mut queue,
1514            &mut visitor,
1515        )
1516    }
1517
1518    /// Up to `max_results` item indices nearest to your query under a custom
1519    /// distance `metric`, nearest first. The zero-copy view counterpart of
1520    /// [`Index2D::neighbors_metric`](crate::Index2D::neighbors_metric); see it for
1521    /// the metric contract and a great-circle example via
1522    /// [`haversine_distance_2d`](crate::haversine_distance_2d).
1523    pub fn neighbors_metric<M: Fn(Box2D) -> f64>(
1524        &self,
1525        metric: M,
1526        max_results: usize,
1527        max_distance: f64,
1528    ) -> Vec<usize> {
1529        let mut results = Vec::new();
1530        self.neighbors_metric_into(metric, max_results, max_distance, &mut results);
1531        results
1532    }
1533
1534    /// [`neighbors_metric`](Self::neighbors_metric) into a reused buffer (cleared first).
1535    pub fn neighbors_metric_into<M: Fn(Box2D) -> f64>(
1536        &self,
1537        metric: M,
1538        max_results: usize,
1539        max_distance: f64,
1540        results: &mut Vec<usize>,
1541    ) {
1542        results.clear();
1543        let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1544        metric_knn::collect_neighbors(
1545            self.num_nodes,
1546            self.num_items,
1547            self.node_size,
1548            |node| self.level_bound_unchecked(self.upper_bound_level(node)),
1549            |pos| self.index_at_unchecked(pos),
1550            max_results,
1551            max_distance,
1552            |pos| metric(self.entry_at_unchecked(pos)),
1553            results,
1554            &mut queue,
1555        );
1556    }
1557
1558    /// Visit items in nondecreasing custom-`metric` distance; the visitor receives
1559    /// the metric distance and may return [`ControlFlow::Break`] to stop early.
1560    /// See [`Index2D::neighbors_metric`](crate::Index2D::neighbors_metric) for the
1561    /// metric contract.
1562    pub fn visit_neighbors_metric<B, M, F>(
1563        &self,
1564        metric: M,
1565        max_distance: f64,
1566        mut visitor: F,
1567    ) -> ControlFlow<B>
1568    where
1569        M: Fn(Box2D) -> f64,
1570        F: FnMut(usize, f64) -> ControlFlow<B>,
1571    {
1572        let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1573        metric_knn::visit_neighbors(
1574            self.num_nodes,
1575            self.num_items,
1576            self.node_size,
1577            |node| self.level_bound_unchecked(self.upper_bound_level(node)),
1578            |pos| self.index_at_unchecked(pos),
1579            max_distance,
1580            |pos| metric(self.entry_at_unchecked(pos)),
1581            &mut queue,
1582            &mut visitor,
1583        )
1584    }
1585
1586    /// Return up to `max_results` item indices nearest to the box `query`.
1587    ///
1588    /// Distance is the box-to-box gap: items overlapping or touching `query`
1589    /// have distance `0.0` and come first (their mutual order is unspecified).
1590    ///
1591    /// # Example
1592    ///
1593    /// ```
1594    /// use packed_spatial_index::{Index2DBuilder, Box2D};
1595    ///
1596    /// let mut builder = Index2DBuilder::new(2);
1597    /// builder.add(Box2D::new(0.0, 0.0, 1.0, 1.0));
1598    /// builder.add(Box2D::new(10.0, 0.0, 11.0, 1.0));
1599    /// let index = builder.finish().unwrap();
1600    ///
1601    /// // The query box's nearest edge is closer to item 1 than to item 0.
1602    /// let query = Box2D::new(7.0, 0.0, 8.0, 1.0);
1603    /// assert_eq!(index.neighbors_of_box(query, 1), vec![1]);
1604    /// ```
1605    pub fn neighbors_of_box(&self, query: Box2D, max_results: usize) -> Vec<usize> {
1606        self.neighbors_of_box_within(query, max_results, f64::INFINITY)
1607    }
1608
1609    /// Return up to `max_results` item indices within `max_distance` of the
1610    /// box `query`. See [`neighbors_of_box`](Self::neighbors_of_box).
1611    pub fn neighbors_of_box_within(
1612        &self,
1613        query: Box2D,
1614        max_results: usize,
1615        max_distance: f64,
1616    ) -> Vec<usize> {
1617        let mut results = Vec::new();
1618        self.neighbors_of_box_into(query, max_results, max_distance, &mut results);
1619        results
1620    }
1621
1622    /// Box-query nearest-neighbor search with a reusable result buffer.
1623    pub fn neighbors_of_box_into(
1624        &self,
1625        query: Box2D,
1626        max_results: usize,
1627        max_distance: f64,
1628        results: &mut Vec<usize>,
1629    ) {
1630        results.clear();
1631        if max_results == 0 {
1632            return;
1633        }
1634        if max_results == 1 {
1635            let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1636            if let Some(index) =
1637                self.nearest_one_with_queue(NeighborQuery2D::Box(query), max_distance, &mut queue)
1638            {
1639                results.push(index);
1640            }
1641            return;
1642        }
1643
1644        let mut item_queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1645        let mut node_queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1646        self.collect_neighbors_with_queues(
1647            NeighborQuery2D::Box(query),
1648            max_results,
1649            max_distance,
1650            results,
1651            &mut item_queue,
1652            &mut node_queue,
1653        );
1654    }
1655
1656    /// Box-query nearest-neighbor search with reusable result and
1657    /// priority-queue buffers.
1658    pub fn neighbors_of_box_with<'na>(
1659        &self,
1660        query: Box2D,
1661        max_results: usize,
1662        max_distance: f64,
1663        workspace: &'na mut NeighborWorkspace,
1664    ) -> &'na [usize] {
1665        workspace.results.clear();
1666        if max_results == 0 {
1667            workspace.queue.clear();
1668            workspace.node_queue.clear();
1669            return &workspace.results;
1670        }
1671        if max_results == 1 {
1672            workspace.queue.clear();
1673            if let Some(index) = self.nearest_one_with_queue(
1674                NeighborQuery2D::Box(query),
1675                max_distance,
1676                &mut workspace.node_queue,
1677            ) {
1678                workspace.results.push(index);
1679            }
1680            return &workspace.results;
1681        }
1682
1683        self.collect_neighbors_with_queues(
1684            NeighborQuery2D::Box(query),
1685            max_results,
1686            max_distance,
1687            &mut workspace.results,
1688            &mut workspace.queue,
1689            &mut workspace.node_queue,
1690        );
1691        &workspace.results
1692    }
1693
1694    /// Visit items in nondecreasing box-to-box distance order from `query`.
1695    ///
1696    /// The visitor receives squared gap distances (`0.0` for items overlapping
1697    /// the query box). Return [`ControlFlow::Break`] to stop early.
1698    pub fn visit_neighbors_of_box<B, F>(
1699        &self,
1700        query: Box2D,
1701        max_distance: f64,
1702        mut visitor: F,
1703    ) -> ControlFlow<B>
1704    where
1705        F: FnMut(usize, f64) -> ControlFlow<B>,
1706    {
1707        let mut queue = BinaryHeap::with_capacity(DEFAULT_NEIGHBOR_QUEUE_CAPACITY);
1708        self.visit_neighbors_with_queue(
1709            NeighborQuery2D::Box(query),
1710            max_distance,
1711            &mut queue,
1712            &mut visitor,
1713        )
1714    }
1715
1716    /// Visit intersecting items without collecting a result `Vec`.
1717    pub fn visit<B, F>(&self, query: Box2D, visitor: F) -> ControlFlow<B>
1718    where
1719        F: FnMut(usize) -> ControlFlow<B>,
1720    {
1721        let mut stack: Vec<usize> = Vec::with_capacity(DEFAULT_SEARCH_STACK_CAPACITY);
1722        self.visit_with_stack(query, &mut stack, visitor)
1723    }
1724
1725    /// Return every pair `(i, j)` where item `i` of `self` intersects item `j`
1726    /// of `other`. See [`Index2D::join`].
1727    pub fn join(&self, other: &Index2DView<'_>) -> Vec<(usize, usize)> {
1728        let mut out = Vec::new();
1729        let _: ControlFlow<()> = self.join_with(other, |i, j| {
1730            out.push((i, j));
1731            ControlFlow::Continue(())
1732        });
1733        out
1734    }
1735
1736    /// Visit every intersecting pair between `self` and `other`. See
1737    /// [`Index2D::join_with`].
1738    pub fn join_with<B, F>(&self, other: &Index2DView<'_>, visitor: F) -> ControlFlow<B>
1739    where
1740        F: FnMut(usize, usize) -> ControlFlow<B>,
1741    {
1742        join_core(self, other, visitor)
1743    }
1744
1745    /// Return every unordered pair of distinct intersecting items within this
1746    /// view, each pair exactly once. See [`Index2D::self_join`].
1747    pub fn self_join(&self) -> Vec<(usize, usize)> {
1748        let mut out = Vec::new();
1749        let _: ControlFlow<()> = self.self_join_with(|i, j| {
1750            out.push((i, j));
1751            ControlFlow::Continue(())
1752        });
1753        out
1754    }
1755
1756    /// Visit every unordered pair of distinct intersecting items within this
1757    /// view. See [`Index2D::self_join_with`].
1758    pub fn self_join_with<B, F>(&self, visitor: F) -> ControlFlow<B>
1759    where
1760        F: FnMut(usize, usize) -> ControlFlow<B>,
1761    {
1762        self_join_core(self, visitor)
1763    }
1764
1765    fn collect_neighbors_with_queues(
1766        &self,
1767        query: NeighborQuery2D,
1768        max_results: usize,
1769        max_distance: f64,
1770        results: &mut Vec<usize>,
1771        item_queue: &mut BinaryHeap<NeighborState>,
1772        node_queue: &mut BinaryHeap<NeighborNodeState>,
1773    ) {
1774        best_first::collect_neighbors_two_queue(
1775            self.num_nodes,
1776            self.num_items,
1777            self.node_size,
1778            |n| self.level_bound_unchecked(self.upper_bound_level(n)),
1779            |pos| self.index_at_unchecked(pos),
1780            max_results,
1781            max_distance,
1782            |pos| query.distance_squared_to(self.entry_at_unchecked(pos)),
1783            results,
1784            item_queue,
1785            node_queue,
1786        );
1787    }
1788
1789    fn nearest_one_with_queue(
1790        &self,
1791        query: NeighborQuery2D,
1792        max_distance: f64,
1793        queue: &mut BinaryHeap<NeighborNodeState>,
1794    ) -> Option<usize> {
1795        best_first::nearest_one(
1796            self.num_nodes,
1797            self.num_items,
1798            self.node_size,
1799            |n| self.level_bound_unchecked(self.upper_bound_level(n)),
1800            |pos| self.index_at_unchecked(pos),
1801            max_distance,
1802            |pos| query.distance_squared_to(self.entry_at_unchecked(pos)),
1803            queue,
1804        )
1805    }
1806
1807    #[doc(hidden)]
1808    pub fn search_into_stack(
1809        &self,
1810        query: Box2D,
1811        results: &mut Vec<usize>,
1812        stack: &mut Vec<usize>,
1813    ) {
1814        collect_overlaps(self, query, results, stack);
1815    }
1816
1817    #[doc(hidden)]
1818    pub fn visit_with_stack<B, F>(
1819        &self,
1820        query: Box2D,
1821        stack: &mut Vec<usize>,
1822        visitor: F,
1823    ) -> ControlFlow<B>
1824    where
1825        F: FnMut(usize) -> ControlFlow<B>,
1826    {
1827        visit_overlaps(self, query, stack, visitor)
1828    }
1829
1830    fn visit_neighbors_with_queue<B, F>(
1831        &self,
1832        query: NeighborQuery2D,
1833        max_distance: f64,
1834        queue: &mut BinaryHeap<NeighborState>,
1835        visitor: &mut F,
1836    ) -> ControlFlow<B>
1837    where
1838        F: FnMut(usize, f64) -> ControlFlow<B>,
1839    {
1840        best_first::visit_neighbors(
1841            self.num_nodes,
1842            self.num_items,
1843            self.node_size,
1844            |n| self.level_bound_unchecked(self.upper_bound_level(n)),
1845            |pos| self.index_at_unchecked(pos),
1846            max_distance,
1847            |pos| query.distance_squared_to(self.entry_at_unchecked(pos)),
1848            queue,
1849            visitor,
1850        )
1851    }
1852
1853    fn upper_bound_level(&self, node_index: usize) -> usize {
1854        let mut lo = 0usize;
1855        let mut hi = self.level_count - 1;
1856        while lo < hi {
1857            let mid = (lo + hi) / 2;
1858            if self.level_bound_unchecked(mid) > node_index {
1859                hi = mid;
1860            } else {
1861                lo = mid + 1;
1862            }
1863        }
1864        lo
1865    }
1866
1867    #[inline]
1868    fn level_bound_unchecked(&self, index: usize) -> usize {
1869        self.level_bounds[index]
1870    }
1871
1872    #[inline]
1873    fn entry_at_unchecked(&self, index: usize) -> Box2D {
1874        let offset = index * 32;
1875        Box2D::new(
1876            read_f64_le_unchecked(self.entries, offset),
1877            read_f64_le_unchecked(self.entries, offset + 8),
1878            read_f64_le_unchecked(self.entries, offset + 16),
1879            read_f64_le_unchecked(self.entries, offset + 24),
1880        )
1881    }
1882
1883    #[inline]
1884    fn index_at_unchecked(&self, index: usize) -> usize {
1885        read_u64_le_unchecked(self.indices, index * 8) as usize
1886    }
1887}
1888
1889impl TreeAccess for Index2D {
1890    type Bounds = Box2D;
1891
1892    #[inline]
1893    fn tree_num_items(&self) -> usize {
1894        self.num_items
1895    }
1896    #[inline]
1897    fn tree_num_nodes(&self) -> usize {
1898        self.entries.len()
1899    }
1900    #[inline]
1901    fn tree_node_size(&self) -> usize {
1902        self.node_size
1903    }
1904    #[inline]
1905    fn tree_level_count(&self) -> usize {
1906        self.level_bounds.len()
1907    }
1908    #[inline]
1909    fn tree_level_bound(&self, level: usize) -> usize {
1910        self.level_bounds[level]
1911    }
1912    #[inline]
1913    fn tree_bounds(&self, pos: usize) -> Box2D {
1914        self.entries[pos]
1915    }
1916    #[inline]
1917    fn tree_index(&self, pos: usize) -> usize {
1918        self.indices[pos]
1919    }
1920    #[inline]
1921    fn bounds_overlap(a: Box2D, b: Box2D) -> bool {
1922        a.overlaps(b)
1923    }
1924    #[inline]
1925    fn bounds_contain(outer: Box2D, inner: Box2D) -> bool {
1926        outer.contains(inner)
1927    }
1928}
1929
1930impl TreeAccess for Index2DView<'_> {
1931    type Bounds = Box2D;
1932
1933    #[inline]
1934    fn tree_num_items(&self) -> usize {
1935        self.num_items
1936    }
1937    #[inline]
1938    fn tree_num_nodes(&self) -> usize {
1939        self.num_nodes
1940    }
1941    #[inline]
1942    fn tree_node_size(&self) -> usize {
1943        self.node_size
1944    }
1945    #[inline]
1946    fn tree_level_count(&self) -> usize {
1947        self.level_count
1948    }
1949    #[inline]
1950    fn tree_level_bound(&self, level: usize) -> usize {
1951        self.level_bound_unchecked(level)
1952    }
1953    #[inline]
1954    fn tree_bounds(&self, pos: usize) -> Box2D {
1955        self.entry_at_unchecked(pos)
1956    }
1957    #[inline]
1958    fn tree_index(&self, pos: usize) -> usize {
1959        self.index_at_unchecked(pos)
1960    }
1961    #[inline]
1962    fn bounds_overlap(a: Box2D, b: Box2D) -> bool {
1963        a.overlaps(b)
1964    }
1965    #[inline]
1966    fn bounds_contain(outer: Box2D, inner: Box2D) -> bool {
1967        outer.contains(inner)
1968    }
1969}
1970
1971/// Lazy iterator over the items intersecting a query box, returned by
1972/// [`Index2D::search_iter`].
1973///
1974/// Yields original insertion indices in tree-traversal order, descending the
1975/// tree only as far as the consumer pulls. Holds a small traversal stack
1976/// (`O(depth)`); it allocates no result `Vec`.
1977pub struct Search2DIter<'a> {
1978    index: &'a Index2D,
1979    query: Box2D,
1980    // (node_index, level) pairs still to visit, same encoding as the search stack.
1981    stack: Vec<usize>,
1982    // Half-open entry range of the leaf node currently being scanned.
1983    leaf_pos: usize,
1984    leaf_end: usize,
1985}
1986
1987impl<'a> Search2DIter<'a> {
1988    fn new(index: &'a Index2D, query: Box2D) -> Self {
1989        let mut stack = Vec::with_capacity(DEFAULT_SEARCH_STACK_CAPACITY);
1990        if index.num_items != 0 {
1991            // Seed with the root so `next` drives the descent uniformly.
1992            stack.push(index.entries.len() - 1);
1993            stack.push(index.level_bounds.len() - 1);
1994        }
1995        Self {
1996            index,
1997            query,
1998            stack,
1999            leaf_pos: 0,
2000            leaf_end: 0,
2001        }
2002    }
2003}
2004
2005impl Iterator for Search2DIter<'_> {
2006    type Item = usize;
2007
2008    fn next(&mut self) -> Option<usize> {
2009        let index = self.index;
2010        loop {
2011            // Drain remaining hits in the leaf node currently being scanned.
2012            while self.leaf_pos < self.leaf_end {
2013                let at = self.leaf_pos;
2014                self.leaf_pos += 1;
2015                if index.entries[at].overlaps(self.query) {
2016                    return Some(index.indices[at]);
2017                }
2018            }
2019
2020            // Pop the next node. The stack holds (node_index, level) pairs.
2021            if self.stack.len() < 2 {
2022                return None;
2023            }
2024            let level = self.stack.pop().unwrap();
2025            let node_index = self.stack.pop().unwrap();
2026            let end = (node_index + index.node_size).min(index.level_bounds[level]);
2027
2028            if node_index < index.num_items {
2029                // Leaf node: scan its entries on the next loop turns.
2030                self.leaf_pos = node_index;
2031                self.leaf_end = end;
2032            } else {
2033                // Internal node: push overlapping children reversed so they pop
2034                // in forward order (matching `visit`).
2035                let child_level = level - 1;
2036                for (b, &child) in index.entries[node_index..end]
2037                    .iter()
2038                    .zip(&index.indices[node_index..end])
2039                    .rev()
2040                {
2041                    if b.overlaps(self.query) {
2042                        self.stack.push(child);
2043                        self.stack.push(child_level);
2044                    }
2045                }
2046            }
2047        }
2048    }
2049
2050    fn size_hint(&self) -> (usize, Option<usize>) {
2051        // Exact count is unknown without traversing; at most every item matches.
2052        (0, Some(self.index.num_items))
2053    }
2054}
2055
2056impl std::iter::FusedIterator for Search2DIter<'_> {}