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
14pub 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
28pub 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(), }
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 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 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 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 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 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, on: bool, }
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 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 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 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 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 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}