Skip to main content

geometry_rs/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod next_after;
4
5use crate::next_after::NextAfter;
6use rtree_rs::{RTree, Rect as RTreeRect};
7
8#[derive(Copy, Clone, Debug, PartialEq)]
9pub struct Point<T = f64> {
10    pub x: T,
11    pub y: T,
12}
13
14/// A coordinate stored as an integer with an application-defined scale.
15pub type I32Point = Point<i32>;
16
17pub trait ContainsPoint<Q> {
18    fn contains_point(&self, point: Q) -> bool;
19}
20
21#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
22pub enum I32RaycastMode {
23    #[default]
24    Float,
25    Integer,
26}
27
28/// A compact polygon whose coordinates are scaled integers.
29///
30/// The raycast can convert segment endpoints to `f64` in-register or scale the
31/// query once and use integer cross products. Both modes use eight bytes per
32/// stored point.
33pub struct I32Polygon {
34    exterior: Vec<I32Point>,
35    holes: Vec<Vec<I32Point>>,
36    min: I32Point,
37    max: I32Point,
38    scale: f64,
39    raycast_mode: I32RaycastMode,
40}
41
42impl I32Polygon {
43    pub fn new(exterior: Vec<I32Point>, holes: Vec<Vec<I32Point>>, scale: f64) -> Self {
44        Self::new_with_mode(exterior, holes, scale, I32RaycastMode::default())
45    }
46
47    pub fn new_with_mode(
48        exterior: Vec<I32Point>,
49        holes: Vec<Vec<I32Point>>,
50        scale: f64,
51        raycast_mode: I32RaycastMode,
52    ) -> Self {
53        assert!(!exterior.is_empty(), "polygon exterior must not be empty");
54        assert!(
55            scale.is_finite() && scale > 0.0,
56            "scale must be positive and finite"
57        );
58        let mut min = exterior[0];
59        let mut max = exterior[0];
60        for point in &exterior {
61            min.x = min.x.min(point.x);
62            min.y = min.y.min(point.y);
63            max.x = max.x.max(point.x);
64            max.y = max.y.max(point.y);
65        }
66        Self {
67            exterior,
68            holes,
69            min,
70            max,
71            scale,
72            raycast_mode,
73        }
74    }
75
76    pub fn contains_point(&self, point: Point) -> bool {
77        match self.raycast_mode {
78            I32RaycastMode::Float => self.contains_point_float(point),
79            I32RaycastMode::Integer => self.contains_point_integer(point),
80        }
81    }
82
83    fn contains_point_float(&self, point: Point) -> bool {
84        let scaled = Point {
85            x: point.x * self.scale,
86            y: point.y * self.scale,
87        };
88        if scaled.x < f64::from(self.min.x)
89            || scaled.x > f64::from(self.max.x)
90            || scaled.y < f64::from(self.min.y)
91            || scaled.y > f64::from(self.max.y)
92            || !i32_ring_contains_point_float(&self.exterior, scaled, false)
93        {
94            return false;
95        }
96        !self
97            .holes
98            .iter()
99            .any(|ring| i32_ring_contains_point_float(ring, scaled, false))
100    }
101
102    fn contains_point_integer(&self, point: Point) -> bool {
103        if !point.x.is_finite() || !point.y.is_finite() {
104            return false;
105        }
106        let scaled = Point {
107            x: (point.x * self.scale).round() as i64,
108            y: (point.y * self.scale).round() as i64,
109        };
110        if scaled.x < i64::from(self.min.x)
111            || scaled.x > i64::from(self.max.x)
112            || scaled.y < i64::from(self.min.y)
113            || scaled.y > i64::from(self.max.y)
114            || !i32_ring_contains_point_integer(&self.exterior, scaled, false)
115        {
116            return false;
117        }
118        !self
119            .holes
120            .iter()
121            .any(|ring| i32_ring_contains_point_integer(ring, scaled, false))
122    }
123
124    pub fn exterior(&self) -> &[I32Point] {
125        &self.exterior
126    }
127
128    pub fn holes(&self) -> &[Vec<I32Point>] {
129        &self.holes
130    }
131
132    pub fn scale(&self) -> f64 {
133        self.scale
134    }
135}
136
137impl ContainsPoint<Point> for I32Polygon {
138    fn contains_point(&self, point: Point) -> bool {
139        I32Polygon::contains_point(self, point)
140    }
141}
142
143#[inline]
144fn i32_ring_contains_point_float(ring: &[I32Point], point: Point, allow_on_edge: bool) -> bool {
145    let mut inside = false;
146    for pair in ring.windows(2) {
147        let segment = Segment {
148            a: Point {
149                x: f64::from(pair[0].x),
150                y: f64::from(pair[0].y),
151            },
152            b: Point {
153                x: f64::from(pair[1].x),
154                y: f64::from(pair[1].y),
155            },
156        };
157        let min_y = segment.a.y.min(segment.b.y);
158        let max_y = segment.a.y.max(segment.b.y);
159        if point.y < min_y || point.y > max_y {
160            continue;
161        }
162        let result = raycast(&segment, point);
163        if result.on {
164            return allow_on_edge;
165        }
166        if result.inside {
167            inside = !inside;
168        }
169    }
170    inside
171}
172
173#[inline]
174fn i32_ring_contains_point_integer(
175    ring: &[I32Point],
176    point: Point<i64>,
177    allow_on_edge: bool,
178) -> bool {
179    let mut inside = false;
180    for pair in ring.windows(2) {
181        let ax = i64::from(pair[0].x);
182        let ay = i64::from(pair[0].y);
183        let bx = i64::from(pair[1].x);
184        let by = i64::from(pair[1].y);
185        let cross = (bx - ax) * (point.y - ay) - (by - ay) * (point.x - ax);
186        if cross == 0
187            && point.x >= ax.min(bx)
188            && point.x <= ax.max(bx)
189            && point.y >= ay.min(by)
190            && point.y <= ay.max(by)
191        {
192            return allow_on_edge;
193        }
194        if (ay > point.y) != (by > point.y) && (cross > 0) == (by > ay) {
195            inside = !inside;
196        }
197    }
198    inside
199}
200
201#[derive(Copy, Clone, Debug)]
202pub struct Rect {
203    pub min: Point,
204    pub max: Point,
205}
206
207impl Rect {
208    pub fn contains_point(&self, p: Point) -> bool {
209        return p.x >= self.min.x && p.x <= self.max.x && p.y >= self.min.y && p.y <= self.max.y;
210    }
211
212    pub fn intersects_rect(&self, other: Rect) -> bool {
213        if self.min.y > other.max.y || self.max.y < other.min.y {
214            return false;
215        }
216        if self.min.x > other.max.x || self.max.x < other.min.x {
217            return false;
218        }
219        return true;
220    }
221
222    pub fn nw(&self) -> Point {
223        Point {
224            x: self.min.x,
225            y: self.max.y,
226        }
227    }
228
229    pub fn sw(&self) -> Point {
230        Point {
231            x: self.min.x,
232            y: self.min.y,
233        }
234    }
235
236    pub fn se(&self) -> Point {
237        Point {
238            x: self.max.x,
239            y: self.min.y,
240        }
241    }
242
243    pub fn ne(&self) -> Point {
244        Point {
245            x: self.max.x,
246            y: self.max.y,
247        }
248    }
249
250    pub fn south(&self) -> Segment {
251        Segment {
252            a: self.sw(),
253            b: self.se(),
254        }
255    }
256
257    pub fn east(&self) -> Segment {
258        Segment {
259            a: self.se(),
260            b: self.ne(),
261        }
262    }
263
264    pub fn north(&self) -> Segment {
265        Segment {
266            a: self.ne(),
267            b: self.nw(),
268        }
269    }
270
271    pub fn west(&self) -> Segment {
272        Segment {
273            a: self.nw(),
274            b: self.sw(),
275        }
276    }
277
278    pub fn segment_at(&self, index: i64) -> Segment {
279        match index {
280            0 => self.south(),
281            1 => self.east(),
282            2 => self.north(),
283            3 => self.west(),
284            _ => self.south(), // TODO(ringsaturn): raise err
285        }
286    }
287}
288
289#[derive(Copy, Clone, Debug)]
290pub struct PolygonBuildOptions {
291    pub enable_rtree: bool,
292    pub enable_compressed_quad: bool,
293    pub enable_y_stripes: bool,
294    pub rtree_min_segments: usize,
295}
296
297impl Default for PolygonBuildOptions {
298    fn default() -> Self {
299        Self {
300            enable_rtree: false,
301            enable_compressed_quad: true,
302            enable_y_stripes: false,
303            rtree_min_segments: 64,
304        }
305    }
306}
307
308#[derive(Clone, Debug, Default, PartialEq, Eq)]
309pub struct YStripesBuildStats {
310    pub segment_count: usize,
311    pub stripe_count: usize,
312    pub assigned_item_count: usize,
313    pub max_bucket_len: usize,
314}
315
316#[derive(Clone, Debug, Default, PartialEq, Eq)]
317pub struct RingBuildStats {
318    pub segment_count: usize,
319    pub below_threshold: bool,
320    pub used_rtree: bool,
321    pub used_compressed_quad: bool,
322    pub used_y_stripes: bool,
323    pub y_stripes: Option<YStripesBuildStats>,
324}
325
326#[derive(Clone, Debug, Default, PartialEq, Eq)]
327pub struct PolygonIndexStats {
328    pub exterior: RingBuildStats,
329    pub holes: Vec<RingBuildStats>,
330}
331
332struct RingIndex {
333    x_min: f64,
334    x_max: f64,
335    seg_count: usize,
336    rtree: Option<RTree<2, f64, usize>>,
337    compressed_quad: Option<CompressedQuadIndex>,
338    y_stripes: Option<YStripesIndex>,
339}
340
341impl RingIndex {
342    fn search_candidates(&self, ring: &[Point], point_y: f64, out: &mut Vec<usize>) {
343        let query_rect = Rect {
344            min: Point {
345                x: self.x_min,
346                y: point_y,
347            },
348            max: Point {
349                x: self.x_max,
350                y: point_y,
351            },
352        };
353
354        let mut seen = vec![false; self.seg_count];
355
356        if let Some(tree) = self.rtree.as_ref() {
357            let query = RTreeRect::new([self.x_min, point_y], [self.x_max, point_y]);
358            for item in tree.search(query) {
359                let idx = *item.data;
360                if idx < self.seg_count && !seen[idx] {
361                    seen[idx] = true;
362                    out.push(idx);
363                }
364            }
365        }
366
367        if let Some(index) = self.y_stripes.as_ref() {
368            let mut tmp = Vec::new();
369            index.search(ring, point_y, &mut tmp);
370            for idx in tmp {
371                if idx < self.seg_count && !seen[idx] {
372                    seen[idx] = true;
373                    out.push(idx);
374                }
375            }
376        }
377
378        if let Some(index) = self.compressed_quad.as_ref() {
379            let mut tmp = Vec::new();
380            index.search_intersects(query_rect, &mut tmp);
381            for idx in tmp {
382                if idx < self.seg_count && !seen[idx] {
383                    seen[idx] = true;
384                    out.push(idx);
385                }
386            }
387        }
388    }
389}
390
391#[derive(Clone, Copy, Default)]
392struct YStripe {
393    start: u32,
394    count: u32,
395}
396
397struct YStripesIndex {
398    min_y: f64,
399    height: f64,
400    stripes: Vec<YStripe>,
401    indexes: Vec<u32>,
402}
403
404impl YStripesIndex {
405    fn build(ring: &[Point], seg_rects: &[Rect]) -> Option<(Self, YStripesBuildStats)> {
406        if seg_rects.is_empty() {
407            return None;
408        }
409
410        let mut min_y = seg_rects[0].min.y;
411        let mut max_y = seg_rects[0].max.y;
412        for rect in seg_rects.iter().copied() {
413            min_y = min_y.min(rect.min.y);
414            max_y = max_y.max(rect.max.y);
415        }
416
417        let mut stripe_count = calc_y_stripe_count(ring, seg_rects.len());
418        if stripe_count == 0 {
419            stripe_count = 1;
420        }
421
422        let height = max_y - min_y;
423        let mut counts = vec![0usize; stripe_count];
424        for rect in seg_rects.iter().copied() {
425            let (start, end) = stripe_bounds_for_rect(rect, min_y, height, stripe_count);
426            for stripe in start..=end {
427                counts[stripe] += 1;
428            }
429        }
430
431        let mut stripes = vec![YStripe::default(); stripe_count];
432        let mut offset = 0usize;
433        for (stripe, count) in stripes.iter_mut().zip(&counts) {
434            stripe.start = u32::try_from(offset).ok()?;
435            stripe.count = 0;
436            offset += *count;
437        }
438        // Segment indices and stripe offsets are stored as u32.
439        if u32::try_from(offset).is_err() || u32::try_from(seg_rects.len()).is_err() {
440            return None;
441        }
442
443        let mut indexes = vec![0u32; offset];
444        for (idx, rect) in seg_rects.iter().copied().enumerate() {
445            let (start, end) = stripe_bounds_for_rect(rect, min_y, height, stripe_count);
446            for stripe_index in start..=end {
447                let stripe = &mut stripes[stripe_index];
448                indexes[(stripe.start + stripe.count) as usize] = idx as u32;
449                stripe.count += 1;
450            }
451        }
452
453        let mut stats = YStripesBuildStats {
454            segment_count: seg_rects.len(),
455            stripe_count,
456            assigned_item_count: indexes.len(),
457            max_bucket_len: counts.into_iter().max().unwrap_or(0),
458        };
459        if stats.stripe_count == 0 {
460            stats.stripe_count = 1;
461        }
462
463        Some((
464            Self {
465                min_y,
466                height,
467                stripes,
468                indexes,
469            },
470            stats,
471        ))
472    }
473
474    fn search(&self, ring: &[Point], y: f64, out: &mut Vec<usize>) {
475        if self.height == 0.0 {
476            if y != self.min_y {
477                return;
478            }
479        } else if y < self.min_y || y > self.min_y + self.height {
480            return;
481        }
482
483        let stripe_index = if self.height == 0.0 {
484            0
485        } else {
486            let raw = ((y - self.min_y) / self.height * self.stripes.len() as f64).floor() as isize;
487            raw.clamp(0, self.stripes.len() as isize - 1) as usize
488        };
489        let stripe = self.stripes[stripe_index];
490        let start = stripe.start as usize;
491        let end = start + stripe.count as usize;
492        for idx in &self.indexes[start..end] {
493            let idx = *idx as usize;
494            // Recompute the segment's y range from its endpoints instead of
495            // storing it; the endpoints are about to be fetched for the
496            // raycast anyway.
497            let a_y = ring[idx].y;
498            let b_y = ring[idx + 1].y;
499            let (seg_min_y, seg_max_y) = if a_y <= b_y { (a_y, b_y) } else { (b_y, a_y) };
500            if y >= seg_min_y && y <= seg_max_y {
501                out.push(idx);
502            }
503        }
504    }
505}
506
507fn stripe_bounds_for_rect(
508    rect: Rect,
509    min_y: f64,
510    height: f64,
511    stripe_count: usize,
512) -> (usize, usize) {
513    if stripe_count <= 1 || height == 0.0 {
514        return (0, 0);
515    }
516
517    let last = stripe_count - 1;
518    let start = (((rect.min.y - min_y) / height) * stripe_count as f64).floor() as isize;
519    let end = (((rect.max.y - min_y) / height) * stripe_count as f64).floor() as isize;
520    (
521        start.clamp(0, last as isize) as usize,
522        end.clamp(0, last as isize) as usize,
523    )
524}
525
526fn calc_ring_area_and_perimeter(ring: &[Point]) -> (f64, f64) {
527    let seg_count = ring_segment_count(ring);
528    if seg_count == 0 {
529        return (0.0, 0.0);
530    }
531
532    let mut signed_area = 0.0;
533    let mut perimeter = 0.0;
534    for i in 0..seg_count {
535        let a = ring[i];
536        let b = ring[i + 1];
537        signed_area += a.x * b.y - b.x * a.y;
538        perimeter += ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt();
539    }
540    (signed_area.abs() * 0.5, perimeter)
541}
542
543fn calc_y_stripe_count(ring: &[Point], seg_count: usize) -> usize {
544    let (area, perimeter) = calc_ring_area_and_perimeter(ring);
545    let mut score = 0.0;
546    if perimeter > 0.0 {
547        score = (area * std::f64::consts::PI * 4.0) / (perimeter * perimeter);
548    }
549    ((seg_count as f64 * score).floor() as usize).max(32)
550}
551
552const Q_MAX_ITEMS: usize = 12;
553const Q_MAX_DEPTH: usize = 64;
554
555#[derive(Default)]
556struct QuadNode {
557    split: bool,
558    items: Vec<usize>,
559    quads: [Option<Box<QuadNode>>; 4],
560}
561
562impl QuadNode {
563    fn new() -> Self {
564        Self {
565            split: false,
566            items: Vec::new(),
567            quads: [None, None, None, None],
568        }
569    }
570}
571
572struct CompressedQuadIndex {
573    bounds: Rect,
574    seg_rects: Vec<Rect>,
575    data: Vec<u8>,
576}
577
578impl CompressedQuadIndex {
579    fn build(ring: &[Point]) -> Option<Self> {
580        let seg_count = ring_segment_count(ring);
581        if seg_count == 0 {
582            return None;
583        }
584
585        let mut min_x = ring[0].x;
586        let mut min_y = ring[0].y;
587        let mut max_x = ring[0].x;
588        let mut max_y = ring[0].y;
589
590        for p in ring.iter().take(seg_count) {
591            if p.x < min_x {
592                min_x = p.x;
593            }
594            if p.y < min_y {
595                min_y = p.y;
596            }
597            if p.x > max_x {
598                max_x = p.x;
599            }
600            if p.y > max_y {
601                max_y = p.y;
602            }
603        }
604
605        let bounds = Rect {
606            min: Point { x: min_x, y: min_y },
607            max: Point { x: max_x, y: max_y },
608        };
609
610        let mut seg_rects = Vec::with_capacity(seg_count);
611        for i in 0..seg_count {
612            seg_rects.push(segment_at_for_slice(ring, i).rect());
613        }
614
615        let mut root = QuadNode::new();
616        for i in 0..seg_rects.len() {
617            insert_quad_node(&mut root, bounds, &seg_rects, i, 0);
618        }
619
620        let mut data = Vec::with_capacity(seg_rects.len() * 2);
621        compress_quad_node(&root, &mut data);
622
623        Some(Self {
624            bounds,
625            seg_rects,
626            data,
627        })
628    }
629
630    fn search_intersects(&self, query: Rect, out: &mut Vec<usize>) {
631        if !self.bounds.intersects_rect(query) {
632            return;
633        }
634        let _ = self.search_intersects_from(0, self.bounds, query, out);
635    }
636
637    fn search_intersects_from(
638        &self,
639        mut addr: usize,
640        bounds: Rect,
641        query: Rect,
642        out: &mut Vec<usize>,
643    ) -> Option<usize> {
644        let (nitems, next_addr) = read_uvarint(&self.data, addr)?;
645        addr = next_addr;
646
647        let mut last: usize = 0;
648        for _ in 0..nitems {
649            let (delta, next_addr) = read_uvarint(&self.data, addr)?;
650            addr = next_addr;
651            last = last.checked_add(delta as usize)?;
652            let seg_rect = self.seg_rects.get(last)?;
653            if seg_rect.intersects_rect(query) {
654                out.push(last);
655            }
656        }
657
658        let split = *self.data.get(addr)?;
659        addr += 1;
660        if split == 0 {
661            return Some(addr);
662        }
663        if split != 1 {
664            return None;
665        }
666
667        for q in 0..4 {
668            let (qsize, next_addr) = read_uvarint(&self.data, addr)?;
669            addr = next_addr;
670            if qsize == 0 {
671                continue;
672            }
673
674            let qsize = usize::try_from(qsize).ok()?;
675            let qbounds = quad_bounds(bounds, q);
676            let child_start = addr;
677            let child_end = child_start.checked_add(qsize)?;
678            if child_end > self.data.len() {
679                return None;
680            }
681
682            if qbounds.intersects_rect(query) {
683                let _ = self.search_intersects_from(child_start, qbounds, query, out)?;
684            }
685            addr = child_end;
686        }
687
688        Some(addr)
689    }
690}
691
692fn ring_segment_count(ring: &[Point]) -> usize {
693    ring.len().saturating_sub(1)
694}
695
696fn segment_at_for_slice(ring: &[Point], index: usize) -> Segment {
697    Segment {
698        a: ring[index],
699        b: ring[index + 1],
700    }
701}
702
703fn build_ring_index(
704    ring: &[Point],
705    options: &PolygonBuildOptions,
706) -> (Option<RingIndex>, RingBuildStats) {
707    let seg_count = ring_segment_count(ring);
708    let index_requested =
709        options.enable_rtree || options.enable_compressed_quad || options.enable_y_stripes;
710    let mut stats = RingBuildStats {
711        segment_count: seg_count,
712        below_threshold: index_requested && seg_count < options.rtree_min_segments,
713        ..RingBuildStats::default()
714    };
715
716    if !index_requested || ring.is_empty() || seg_count < options.rtree_min_segments {
717        return (None, stats);
718    }
719
720    let mut x_min = ring[0].x;
721    let mut x_max = ring[0].x;
722    for p in ring.iter().take(seg_count) {
723        if p.x < x_min {
724            x_min = p.x;
725        }
726        if p.x > x_max {
727            x_max = p.x;
728        }
729    }
730
731    let rtree = if options.enable_rtree {
732        stats.used_rtree = true;
733        let mut tree: RTree<2, f64, usize> = RTree::new();
734        for i in 0..seg_count {
735            let seg_rect = segment_at_for_slice(ring, i).rect();
736            tree.insert(
737                RTreeRect::new(
738                    [seg_rect.min.x, seg_rect.min.y],
739                    [seg_rect.max.x, seg_rect.max.y],
740                ),
741                i,
742            );
743        }
744        Some(tree)
745    } else {
746        None
747    };
748
749    let compressed_quad = if options.enable_compressed_quad {
750        match CompressedQuadIndex::build(ring) {
751            Some(index) => {
752                stats.used_compressed_quad = true;
753                Some(index)
754            }
755            None => None,
756        }
757    } else {
758        None
759    };
760
761    let (y_stripes, y_stripes_stats) = if options.enable_y_stripes {
762        let mut seg_rects = Vec::with_capacity(seg_count);
763        for i in 0..seg_count {
764            seg_rects.push(segment_at_for_slice(ring, i).rect());
765        }
766        match YStripesIndex::build(ring, &seg_rects) {
767            Some((index, stripe_stats)) => {
768                stats.used_y_stripes = true;
769                (Some(index), Some(stripe_stats))
770            }
771            None => (None, None),
772        }
773    } else {
774        (None, None)
775    };
776    stats.y_stripes = y_stripes_stats;
777
778    if rtree.is_none() && compressed_quad.is_none() && y_stripes.is_none() {
779        return (None, stats);
780    }
781
782    (
783        Some(RingIndex {
784            x_min,
785            x_max,
786            seg_count,
787            rtree,
788            compressed_quad,
789            y_stripes,
790        }),
791        stats,
792    )
793}
794
795fn rings_contains_point(
796    ring: &[Point],
797    ring_index: Option<&RingIndex>,
798    point: Point,
799    allow_on_edge: bool,
800) -> bool {
801    let mut inside: bool = false;
802
803    if let Some(index) = ring_index {
804        let mut candidates = Vec::new();
805        index.search_candidates(ring, point.y, &mut candidates);
806        for i in candidates {
807            let seg = segment_at_for_slice(ring, i);
808            let res: RaycastResult = raycast(&seg, point);
809            if res.on {
810                inside = allow_on_edge;
811                break;
812            }
813            if res.inside {
814                inside = !inside;
815            }
816        }
817        return inside;
818    }
819
820    let ray_rect = Rect {
821        min: Point {
822            x: std::f64::NEG_INFINITY,
823            y: point.y,
824        },
825        max: Point {
826            x: std::f64::INFINITY,
827            y: point.y,
828        },
829    };
830
831    for pair in ring.windows(2) {
832        let seg = Segment {
833            a: pair[0],
834            b: pair[1],
835        };
836
837        if seg.rect().intersects_rect(ray_rect) {
838            let res: RaycastResult = raycast(&seg, point);
839            if res.on {
840                inside = allow_on_edge;
841                break;
842            }
843            if res.inside {
844                inside = !inside;
845            }
846        }
847    }
848
849    return inside;
850}
851
852fn choose_quad(bounds: Rect, rect: Rect) -> Option<usize> {
853    let mid_x = (bounds.min.x + bounds.max.x) / 2.0;
854    let mid_y = (bounds.min.y + bounds.max.y) / 2.0;
855
856    if rect.max.x < mid_x {
857        if rect.max.y < mid_y {
858            return Some(2);
859        }
860        if rect.min.y < mid_y {
861            return None;
862        }
863        return Some(0);
864    }
865
866    if rect.min.x < mid_x {
867        return None;
868    }
869
870    if rect.max.y < mid_y {
871        return Some(3);
872    }
873    if rect.min.y < mid_y {
874        return None;
875    }
876    Some(1)
877}
878
879fn quad_bounds(mut bounds: Rect, q: usize) -> Rect {
880    let center_x = (bounds.min.x + bounds.max.x) / 2.0;
881    let center_y = (bounds.min.y + bounds.max.y) / 2.0;
882
883    match q {
884        0 => {
885            bounds.min.y = center_y;
886            bounds.max.x = center_x;
887        }
888        1 => {
889            bounds.min.x = center_x;
890            bounds.min.y = center_y;
891        }
892        2 => {
893            bounds.max.x = center_x;
894            bounds.max.y = center_y;
895        }
896        3 => {
897            bounds.min.x = center_x;
898            bounds.max.y = center_y;
899        }
900        _ => {}
901    }
902    bounds
903}
904
905fn insert_quad_node(
906    node: &mut QuadNode,
907    bounds: Rect,
908    seg_rects: &[Rect],
909    item: usize,
910    depth: usize,
911) {
912    if depth == Q_MAX_DEPTH {
913        node.items.push(item);
914        return;
915    }
916
917    let item_rect = seg_rects[item];
918    if node.split {
919        if let Some(q) = choose_quad(bounds, item_rect) {
920            let qbounds = quad_bounds(bounds, q);
921            if node.quads[q].is_none() {
922                node.quads[q] = Some(Box::new(QuadNode::new()));
923            }
924            if let Some(quad) = node.quads[q].as_deref_mut() {
925                insert_quad_node(quad, qbounds, seg_rects, item, depth + 1);
926            }
927        } else {
928            node.items.push(item);
929        }
930        return;
931    }
932
933    if node.items.len() == Q_MAX_ITEMS {
934        let existing = std::mem::take(&mut node.items);
935        node.split = true;
936        for i in existing {
937            let rect = seg_rects[i];
938            if let Some(q) = choose_quad(bounds, rect) {
939                let qbounds = quad_bounds(bounds, q);
940                if node.quads[q].is_none() {
941                    node.quads[q] = Some(Box::new(QuadNode::new()));
942                }
943                if let Some(quad) = node.quads[q].as_deref_mut() {
944                    insert_quad_node(quad, qbounds, seg_rects, i, depth + 1);
945                }
946            } else {
947                node.items.push(i);
948            }
949        }
950        insert_quad_node(node, bounds, seg_rects, item, depth);
951        return;
952    }
953
954    node.items.push(item);
955}
956
957fn append_uvarint(dst: &mut Vec<u8>, mut x: u64) {
958    while x >= 0x80 {
959        dst.push((x as u8 & 0x7f) | 0x80);
960        x >>= 7;
961    }
962    dst.push(x as u8);
963}
964
965fn read_uvarint(data: &[u8], mut addr: usize) -> Option<(u64, usize)> {
966    let mut x: u64 = 0;
967    let mut shift = 0;
968
969    loop {
970        let b = *data.get(addr)?;
971        addr += 1;
972
973        if shift == 70 {
974            return None;
975        }
976
977        x |= ((b & 0x7f) as u64) << shift;
978        if b < 0x80 {
979            return Some((x, addr));
980        }
981        shift += 7;
982    }
983}
984
985fn compress_quad_node(node: &QuadNode, dst: &mut Vec<u8>) {
986    let mut items = node.items.clone();
987    items.sort_unstable();
988
989    append_uvarint(dst, items.len() as u64);
990    let mut last = 0usize;
991    for item in items {
992        append_uvarint(dst, (item - last) as u64);
993        last = item;
994    }
995
996    if !node.split {
997        dst.push(0);
998        return;
999    }
1000
1001    dst.push(1);
1002    for q in 0..4 {
1003        if let Some(child) = node.quads[q].as_deref() {
1004            let mut child_bytes = Vec::new();
1005            compress_quad_node(child, &mut child_bytes);
1006            append_uvarint(dst, child_bytes.len() as u64);
1007            dst.extend_from_slice(&child_bytes);
1008        } else {
1009            append_uvarint(dst, 0);
1010        }
1011    }
1012}
1013
1014pub struct Polygon {
1015    exterior: Vec<Point>,
1016    holes: Vec<Vec<Point>>,
1017    rect: Rect,
1018    options: PolygonBuildOptions,
1019    exterior_index: Option<RingIndex>,
1020    hole_indexes: Vec<Option<RingIndex>>,
1021    index_stats: PolygonIndexStats,
1022}
1023
1024impl Polygon {
1025    fn compute_rect(exterior: &[Point]) -> Rect {
1026        let mut minx: f64 = exterior[0].x;
1027        let mut miny: f64 = exterior[0].y;
1028        let mut maxx: f64 = exterior[0].x;
1029        let mut maxy: f64 = exterior[0].y;
1030
1031        for p in exterior.iter() {
1032            if p.x < minx {
1033                minx = p.x;
1034            }
1035            if p.y < miny {
1036                miny = p.y;
1037            }
1038            if p.x > maxx {
1039                maxx = p.x;
1040            }
1041            if p.y > maxy {
1042                maxy = p.y;
1043            }
1044        }
1045
1046        Rect {
1047            min: Point { x: minx, y: miny },
1048            max: Point { x: maxx, y: maxy },
1049        }
1050    }
1051
1052    fn rebuild_cache(&mut self) {
1053        self.rect = Self::compute_rect(&self.exterior);
1054        let (exterior_index, exterior_stats) = build_ring_index(&self.exterior, &self.options);
1055        self.exterior_index = exterior_index;
1056        self.index_stats.exterior = exterior_stats;
1057
1058        let hole_indexes_and_stats: Vec<(Option<RingIndex>, RingBuildStats)> = self
1059            .holes
1060            .iter()
1061            .map(|hole| build_ring_index(hole, &self.options))
1062            .collect();
1063        let (hole_indexes, hole_stats): (Vec<Option<RingIndex>>, Vec<RingBuildStats>) =
1064            hole_indexes_and_stats.into_iter().unzip();
1065        self.hole_indexes = hole_indexes;
1066        self.index_stats.holes = hole_stats;
1067    }
1068
1069    /// Point-In-Polygon check, the normal way.
1070    /// It's most used algorithm implementation, port from Go's [geojson]
1071    ///
1072    /// [geojson]: https://github.com/tidwall/geojson
1073    fn contains_point_normal(&self, p: Point) -> bool {
1074        if !rings_contains_point(&self.exterior, self.exterior_index.as_ref(), p, false) {
1075            return false;
1076        }
1077
1078        for (hole, hole_index) in self.holes.iter().zip(self.hole_indexes.iter()) {
1079            if rings_contains_point(hole, hole_index.as_ref(), p, false) {
1080                return false;
1081            }
1082        }
1083
1084        return true;
1085    }
1086
1087    /// Do point-in-polygon search.
1088    pub fn contains_point(&self, p: Point) -> bool {
1089        if !self.rect.contains_point(p) {
1090            return false;
1091        }
1092
1093        return self.contains_point_normal(p);
1094    }
1095
1096    /// Create a new Polygon instance from exterior and holes.
1097    ///
1098    /// Example:
1099    ///
1100    /// ```rust
1101    /// use std::vec;
1102    /// use geometry_rs;
1103    /// let poly = geometry_rs::Polygon::new(
1104    ///     vec![
1105    ///         geometry_rs::Point {
1106    ///             x: 90.48826291293898,
1107    ///             y: 45.951129815858565,
1108    ///         },
1109    ///         geometry_rs::Point {
1110    ///             x: 90.48826291293898,
1111    ///             y: 27.99437617512571,
1112    ///         },
1113    ///         geometry_rs::Point {
1114    ///             x: 122.83201291294,
1115    ///             y: 27.99437617512571,
1116    ///         },
1117    ///         geometry_rs::Point {
1118    ///             x: 122.83201291294,
1119    ///             y: 45.951129815858565,
1120    ///         },
1121    ///         geometry_rs::Point {
1122    ///             x: 90.48826291293898,
1123    ///             y: 45.951129815858565,
1124    ///         },
1125    ///     ],
1126    ///     vec![],
1127    ///     None,
1128    /// );
1129    ///
1130    /// let p_out = geometry_rs::Point {
1131    ///     x: 130.74216916294148,
1132    ///     y: 37.649011392900306,
1133    /// };
1134    ///
1135    /// print!("{:?}\n", poly.contains_point(p_out));
1136    ///
1137    /// let p_in = geometry_rs::Point {
1138    ///     x: 99.9804504129416,
1139    ///     y: 39.70716466970461,
1140    /// };
1141    /// print!("{:?}\n", poly.contains_point(p_in));
1142    /// ```
1143    pub fn new(
1144        exterior: Vec<Point>,
1145        holes: Vec<Vec<Point>>,
1146        options: Option<PolygonBuildOptions>,
1147    ) -> Polygon {
1148        let mut poly = Polygon {
1149            exterior,
1150            holes,
1151            rect: Rect {
1152                min: Point { x: 0.0, y: 0.0 },
1153                max: Point { x: 0.0, y: 0.0 },
1154            },
1155            options: options.unwrap_or_default(),
1156            exterior_index: None,
1157            hole_indexes: Vec::new(),
1158            index_stats: PolygonIndexStats::default(),
1159        };
1160        poly.rebuild_cache();
1161        poly
1162    }
1163
1164    pub fn exterior(&self) -> &[Point] {
1165        &self.exterior
1166    }
1167
1168    pub fn holes(&self) -> &[Vec<Point>] {
1169        &self.holes
1170    }
1171
1172    pub fn rect(&self) -> Rect {
1173        self.rect
1174    }
1175
1176    pub fn options(&self) -> PolygonBuildOptions {
1177        self.options
1178    }
1179
1180    pub fn index_stats(&self) -> &PolygonIndexStats {
1181        &self.index_stats
1182    }
1183
1184    pub fn set_exterior(&mut self, exterior: Vec<Point>) {
1185        self.exterior = exterior;
1186        self.rebuild_cache();
1187    }
1188
1189    pub fn set_holes(&mut self, holes: Vec<Vec<Point>>) {
1190        self.holes = holes;
1191        self.rebuild_cache();
1192    }
1193
1194    pub fn set_options(&mut self, options: PolygonBuildOptions) {
1195        self.options = options;
1196        self.rebuild_cache();
1197    }
1198}
1199
1200impl ContainsPoint<Point> for Polygon {
1201    fn contains_point(&self, point: Point) -> bool {
1202        Polygon::contains_point(self, point)
1203    }
1204}
1205
1206#[derive(Copy, Clone, Debug)]
1207pub struct Segment {
1208    pub a: Point,
1209    pub b: Point,
1210}
1211
1212impl Segment {
1213    pub fn rect(&self) -> Rect {
1214        let mut min_x: f64 = self.a.x;
1215        let mut min_y: f64 = self.a.y;
1216        let mut max_x: f64 = self.b.x;
1217        let mut max_y: f64 = self.b.y;
1218
1219        if min_x > max_x {
1220            let actual_min_x = max_x;
1221            let actual_max_x = min_x;
1222            min_x = actual_min_x;
1223            max_x = actual_max_x;
1224        }
1225
1226        if min_y > max_y {
1227            let actual_min_y = max_y;
1228            let actual_max_y = min_y;
1229            min_y = actual_min_y;
1230            max_y = actual_max_y;
1231        }
1232
1233        return Rect {
1234            min: Point { x: min_x, y: min_y },
1235            max: Point { x: max_x, y: max_y },
1236        };
1237    }
1238}
1239
1240pub struct RaycastResult {
1241    inside: bool, // point on the left
1242    on: bool,     // point is directly on top of
1243}
1244
1245pub fn raycast(seg: &Segment, point: Point) -> RaycastResult {
1246    let mut p = point;
1247    let a = seg.a;
1248    let b = seg.b;
1249
1250    // make sure that the point is inside the segment bounds
1251    if a.y < b.y && (p.y < a.y || p.y > b.y) {
1252        return RaycastResult {
1253            inside: false,
1254            on: false,
1255        };
1256    } else if a.y > b.y && (p.y < b.y || p.y > a.y) {
1257        return RaycastResult {
1258            inside: false,
1259            on: false,
1260        };
1261    }
1262
1263    // test if point is in on the segment
1264    if a.y == b.y {
1265        if a.x == b.x {
1266            if p.x == a.x && p.y == a.y {
1267                return RaycastResult {
1268                    inside: false,
1269                    on: true,
1270                };
1271            }
1272            return RaycastResult {
1273                inside: false,
1274                on: false,
1275            };
1276        }
1277        if p.y == b.y {
1278            // horizontal segment
1279            // check if the point in on the line
1280            if a.x < b.x {
1281                if p.x >= a.x && p.x <= b.x {
1282                    return RaycastResult {
1283                        inside: false,
1284                        on: true,
1285                    };
1286                }
1287            } else if p.x >= b.x && p.x <= a.x {
1288                return RaycastResult {
1289                    inside: false,
1290                    on: true,
1291                };
1292            }
1293        }
1294    }
1295    if a.x == b.x && p.x == b.x {
1296        // vertical segment
1297        // check if the point in on the line
1298        if a.y < b.y {
1299            if p.y >= a.y && p.y <= b.y {
1300                return RaycastResult {
1301                    inside: false,
1302                    on: true,
1303                };
1304            }
1305        } else if p.y >= b.y && p.y <= a.y {
1306            return RaycastResult {
1307                inside: false,
1308                on: true,
1309            };
1310        }
1311    }
1312    if (p.x - a.x) / (b.x - a.x) == (p.y - a.y) / (b.y - a.y) {
1313        return RaycastResult {
1314            inside: false,
1315            on: true,
1316        };
1317    }
1318
1319    // do the actual raycast here.
1320    while p.y == a.y || p.y == b.y {
1321        p.y = p.y.next_after(std::f64::INFINITY);
1322    }
1323
1324    if a.y < b.y {
1325        if p.y < a.y || p.y > b.y {
1326            return RaycastResult {
1327                inside: false,
1328                on: false,
1329            };
1330        }
1331    } else if p.y < b.y || p.y > a.y {
1332        return RaycastResult {
1333            inside: false,
1334            on: false,
1335        };
1336    }
1337    if a.x > b.x {
1338        if p.x >= a.x {
1339            return RaycastResult {
1340                inside: false,
1341                on: false,
1342            };
1343        }
1344        if p.x <= b.x {
1345            return RaycastResult {
1346                inside: true,
1347                on: false,
1348            };
1349        }
1350    } else {
1351        if p.x >= b.x {
1352            return RaycastResult {
1353                inside: false,
1354                on: false,
1355            };
1356        }
1357        if p.x <= a.x {
1358            return RaycastResult {
1359                inside: true,
1360                on: false,
1361            };
1362        }
1363    }
1364    if a.y < b.y {
1365        if (p.y - a.y) / (p.x - a.x) >= (b.y - a.y) / (b.x - a.x) {
1366            return RaycastResult {
1367                inside: true,
1368                on: false,
1369            };
1370        }
1371    } else if (p.y - b.y) / (p.x - b.x) >= (a.y - b.y) / (a.x - b.x) {
1372        return RaycastResult {
1373            inside: true,
1374            on: false,
1375        };
1376    }
1377    return RaycastResult {
1378        inside: false,
1379        on: false,
1380    };
1381}
1382
1383#[cfg(test)]
1384mod tests {
1385    use super::*;
1386
1387    fn square(min: f64, max: f64) -> Vec<Point> {
1388        vec![
1389            Point { x: min, y: min },
1390            Point { x: min, y: max },
1391            Point { x: max, y: max },
1392            Point { x: max, y: min },
1393            Point { x: min, y: min },
1394        ]
1395    }
1396
1397    fn polygon_with_segments(segments: usize) -> Vec<Point> {
1398        let mut ring = Vec::with_capacity(segments + 1);
1399        for i in 0..segments {
1400            let theta = (i as f64) / (segments as f64) * std::f64::consts::TAU;
1401            ring.push(Point {
1402                x: theta.cos(),
1403                y: theta.sin(),
1404            });
1405        }
1406        ring.push(ring[0]);
1407        ring
1408    }
1409
1410    fn scan_candidates(ring: &[Point], y: f64, x_min: f64, x_max: f64) -> Vec<usize> {
1411        let mut out = Vec::new();
1412        let query = Rect {
1413            min: Point { x: x_min, y },
1414            max: Point { x: x_max, y },
1415        };
1416        for (i, pair) in ring.windows(2).enumerate() {
1417            let seg = Segment {
1418                a: pair[0],
1419                b: pair[1],
1420            };
1421            if seg.rect().intersects_rect(query) {
1422                out.push(i);
1423            }
1424        }
1425        out
1426    }
1427
1428    #[test]
1429    fn rings_contains_point_allow_on_edge() {
1430        let ring = square(0.0, 10.0);
1431        let on_edge = Point { x: 0.0, y: 5.0 };
1432        assert!(rings_contains_point(&ring, None, on_edge, true));
1433        assert!(!rings_contains_point(&ring, None, on_edge, false));
1434    }
1435
1436    #[test]
1437    fn polygon_contains_basic_in_and_out() {
1438        let poly = Polygon::new(square(0.0, 10.0), vec![], None);
1439        assert!(poly.contains_point(Point { x: 5.0, y: 5.0 }));
1440        assert!(!poly.contains_point(Point { x: 20.0, y: 5.0 }));
1441    }
1442
1443    #[test]
1444    fn polygon_contains_with_hole() {
1445        let poly = Polygon::new(square(0.0, 10.0), vec![square(3.0, 7.0)], None);
1446        assert!(poly.contains_point(Point { x: 1.0, y: 1.0 }));
1447        assert!(!poly.contains_point(Point { x: 5.0, y: 5.0 }));
1448    }
1449
1450    #[test]
1451    fn i32_polygon_contains_with_hole_and_fractional_query() {
1452        let square = |min, max| {
1453            vec![
1454                I32Point { x: min, y: min },
1455                I32Point { x: min, y: max },
1456                I32Point { x: max, y: max },
1457                I32Point { x: max, y: min },
1458                I32Point { x: min, y: min },
1459            ]
1460        };
1461        for mode in [I32RaycastMode::Float, I32RaycastMode::Integer] {
1462            let poly = I32Polygon::new_with_mode(
1463                square(0, 1_000_000),
1464                vec![square(400_000, 600_000)],
1465                1e5,
1466                mode,
1467            );
1468            assert!(poly.contains_point(Point {
1469                x: 1.23456,
1470                y: 2.34567
1471            }));
1472            assert!(!poly.contains_point(Point { x: 5.0, y: 5.0 }));
1473            assert!(!poly.contains_point(Point { x: 11.0, y: 5.0 }));
1474        }
1475    }
1476
1477    #[test]
1478    fn indexed_and_non_indexed_results_match() {
1479        let ring = polygon_with_segments(128);
1480        let p_in = Point { x: 0.2, y: 0.1 };
1481        let p_out = Point { x: 2.0, y: 0.0 };
1482
1483        let p1 = Polygon::new(
1484            ring.clone(),
1485            vec![],
1486            Some(PolygonBuildOptions {
1487                enable_rtree: false,
1488                enable_compressed_quad: false,
1489                enable_y_stripes: false,
1490                rtree_min_segments: 64,
1491            }),
1492        );
1493
1494        let p2 = Polygon::new(
1495            ring,
1496            vec![],
1497            Some(PolygonBuildOptions {
1498                enable_rtree: true,
1499                enable_compressed_quad: true,
1500                enable_y_stripes: false,
1501                rtree_min_segments: 64,
1502            }),
1503        );
1504
1505        assert_eq!(p1.contains_point(p_in), p2.contains_point(p_in));
1506        assert_eq!(p1.contains_point(p_out), p2.contains_point(p_out));
1507    }
1508
1509    #[test]
1510    fn rtree_and_compressed_quad_and_both_match_baseline() {
1511        let ring = polygon_with_segments(160);
1512        let points = [
1513            Point { x: 0.2, y: 0.1 },
1514            Point { x: -0.4, y: -0.3 },
1515            Point { x: 1.2, y: 0.0 },
1516        ];
1517
1518        let base = Polygon::new(
1519            ring.clone(),
1520            vec![],
1521            Some(PolygonBuildOptions {
1522                enable_rtree: false,
1523                enable_compressed_quad: false,
1524                enable_y_stripes: false,
1525                rtree_min_segments: 64,
1526            }),
1527        );
1528        let only_rtree = Polygon::new(
1529            ring.clone(),
1530            vec![],
1531            Some(PolygonBuildOptions {
1532                enable_rtree: true,
1533                enable_compressed_quad: false,
1534                enable_y_stripes: false,
1535                rtree_min_segments: 64,
1536            }),
1537        );
1538        let only_compressed = Polygon::new(
1539            ring.clone(),
1540            vec![],
1541            Some(PolygonBuildOptions {
1542                enable_rtree: false,
1543                enable_compressed_quad: true,
1544                enable_y_stripes: false,
1545                rtree_min_segments: 64,
1546            }),
1547        );
1548        let both = Polygon::new(
1549            ring,
1550            vec![],
1551            Some(PolygonBuildOptions {
1552                enable_rtree: true,
1553                enable_compressed_quad: true,
1554                enable_y_stripes: false,
1555                rtree_min_segments: 64,
1556            }),
1557        );
1558
1559        for p in points {
1560            let expected = base.contains_point(p);
1561            assert_eq!(only_rtree.contains_point(p), expected);
1562            assert_eq!(only_compressed.contains_point(p), expected);
1563            assert_eq!(both.contains_point(p), expected);
1564        }
1565    }
1566
1567    #[test]
1568    fn threshold_boundaries_63_64_65_are_consistent() {
1569        let ring = polygon_with_segments(65);
1570        let p_in = Point { x: 0.2, y: 0.0 };
1571        let p_out = Point { x: 1.5, y: 0.0 };
1572
1573        let p63 = Polygon::new(
1574            ring.clone(),
1575            vec![],
1576            Some(PolygonBuildOptions {
1577                enable_rtree: true,
1578                enable_compressed_quad: true,
1579                enable_y_stripes: false,
1580                rtree_min_segments: 63,
1581            }),
1582        );
1583        let p64 = Polygon::new(
1584            ring.clone(),
1585            vec![],
1586            Some(PolygonBuildOptions {
1587                enable_rtree: true,
1588                enable_compressed_quad: true,
1589                enable_y_stripes: false,
1590                rtree_min_segments: 64,
1591            }),
1592        );
1593        let p65 = Polygon::new(
1594            ring,
1595            vec![],
1596            Some(PolygonBuildOptions {
1597                enable_rtree: true,
1598                enable_compressed_quad: true,
1599                enable_y_stripes: false,
1600                rtree_min_segments: 65,
1601            }),
1602        );
1603
1604        assert_eq!(p63.contains_point(p_in), p64.contains_point(p_in));
1605        assert_eq!(p64.contains_point(p_in), p65.contains_point(p_in));
1606
1607        assert_eq!(p63.contains_point(p_out), p64.contains_point(p_out));
1608        assert_eq!(p64.contains_point(p_out), p65.contains_point(p_out));
1609    }
1610
1611    #[test]
1612    fn setters_rebuild_cache_and_keep_correct_results() {
1613        let mut poly = Polygon::new(square(0.0, 10.0), vec![], None);
1614        assert!(poly.contains_point(Point { x: 1.0, y: 1.0 }));
1615
1616        poly.set_exterior(square(20.0, 30.0));
1617        assert!(!poly.contains_point(Point { x: 1.0, y: 1.0 }));
1618        assert!(poly.contains_point(Point { x: 21.0, y: 21.0 }));
1619
1620        poly.set_holes(vec![square(22.0, 24.0)]);
1621        assert!(!poly.contains_point(Point { x: 23.0, y: 23.0 }));
1622
1623        poly.set_options(PolygonBuildOptions {
1624            enable_rtree: false,
1625            enable_compressed_quad: false,
1626            enable_y_stripes: false,
1627            rtree_min_segments: 64,
1628        });
1629        assert!(!poly.contains_point(Point { x: 23.0, y: 23.0 }));
1630        assert!(poly.contains_point(Point { x: 25.0, y: 25.0 }));
1631    }
1632
1633    #[test]
1634    fn compressed_quad_candidates_match_scan() {
1635        let ring = polygon_with_segments(256);
1636        let options = PolygonBuildOptions {
1637            enable_rtree: false,
1638            enable_compressed_quad: true,
1639            enable_y_stripes: false,
1640            rtree_min_segments: 64,
1641        };
1642        let (index, stats) = build_ring_index(&ring, &options);
1643        let index = index.unwrap();
1644
1645        assert!(stats.used_compressed_quad);
1646        assert!(!stats.below_threshold);
1647
1648        for y in [-1.2, -1.0, -0.75, -0.2, 0.0, 0.4, 0.99, 1.0, 1.2] {
1649            let mut from_index = Vec::new();
1650            index.search_candidates(&ring, y, &mut from_index);
1651            from_index.sort_unstable();
1652
1653            let mut from_scan = scan_candidates(&ring, y, index.x_min, index.x_max);
1654            from_scan.sort_unstable();
1655
1656            assert_eq!(from_index, from_scan);
1657        }
1658    }
1659
1660    #[test]
1661    fn y_stripes_candidates_match_scan() {
1662        let ring = polygon_with_segments(256);
1663        let options = PolygonBuildOptions {
1664            enable_rtree: false,
1665            enable_compressed_quad: false,
1666            enable_y_stripes: true,
1667            rtree_min_segments: 64,
1668        };
1669        let (index, stats) = build_ring_index(&ring, &options);
1670        let index = index.unwrap();
1671
1672        assert!(stats.used_y_stripes);
1673        assert!(!stats.below_threshold);
1674        let y_stats = stats.y_stripes.as_ref().unwrap();
1675        assert_eq!(y_stats.segment_count, 256);
1676        assert!(y_stats.stripe_count >= 32);
1677        assert!(y_stats.assigned_item_count >= 256);
1678        assert!(y_stats.max_bucket_len > 0);
1679
1680        for y in [-1.2, -1.0, -0.75, -0.2, 0.0, 0.4, 0.99, 1.0, 1.2] {
1681            let mut from_index = Vec::new();
1682            index.search_candidates(&ring, y, &mut from_index);
1683            from_index.sort_unstable();
1684
1685            let mut from_scan = scan_candidates(&ring, y, index.x_min, index.x_max);
1686            from_scan.sort_unstable();
1687
1688            assert_eq!(from_index, from_scan);
1689        }
1690    }
1691
1692    #[test]
1693    fn polygon_index_stats_capture_threshold_for_compressed_quad() {
1694        let indexed = Polygon::new(
1695            polygon_with_segments(256),
1696            vec![polygon_with_segments(32)],
1697            Some(PolygonBuildOptions {
1698                enable_rtree: false,
1699                enable_compressed_quad: true,
1700                enable_y_stripes: false,
1701                rtree_min_segments: 64,
1702            }),
1703        );
1704        let stats = indexed.index_stats();
1705        assert!(stats.exterior.used_compressed_quad);
1706        assert!(!stats.exterior.below_threshold);
1707        assert_eq!(stats.exterior.segment_count, 256);
1708        assert_eq!(stats.holes.len(), 1);
1709        assert!(stats.holes[0].below_threshold);
1710        assert!(!stats.holes[0].used_compressed_quad);
1711    }
1712
1713    #[test]
1714    fn polygon_index_stats_capture_y_stripes_metrics() {
1715        let indexed = Polygon::new(
1716            polygon_with_segments(256),
1717            vec![polygon_with_segments(32)],
1718            Some(PolygonBuildOptions {
1719                enable_rtree: false,
1720                enable_compressed_quad: false,
1721                enable_y_stripes: true,
1722                rtree_min_segments: 64,
1723            }),
1724        );
1725        let stats = indexed.index_stats();
1726        assert!(stats.exterior.used_y_stripes);
1727        assert!(!stats.exterior.below_threshold);
1728        assert_eq!(stats.exterior.segment_count, 256);
1729        assert!(stats.exterior.y_stripes.is_some());
1730        assert_eq!(stats.holes.len(), 1);
1731        assert!(stats.holes[0].below_threshold);
1732        assert!(!stats.holes[0].used_y_stripes);
1733        assert!(stats.holes[0].y_stripes.is_none());
1734    }
1735}