1mod boundary;
53mod union_find;
54
55use std::collections::HashMap;
56
57use oxigdal_core::types::GeoTransform;
58use oxigdal_core::vector::{Coordinate, LineString, MultiPolygon, Polygon};
59
60use crate::error::{AlgorithmError, Result};
61
62pub use boundary::Connectivity;
63use boundary::{
64 ClassifiedPolygon, extract_pixel_edge_boundaries, pixel_coords_to_coordinates,
65 trace_boundaries, transform_coords,
66};
67use union_find::UnionFind;
68
69#[derive(Debug, Clone)]
75pub struct PolygonizeOptions {
76 pub connectivity: Connectivity,
79
80 pub nodata: Option<f64>,
84
85 pub nodata_tolerance: f64,
88
89 pub transform: Option<GeoTransform>,
92
93 pub simplify_tolerance: f64,
96
97 pub min_area: f64,
100
101 pub boundary_method: BoundaryMethod,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum BoundaryMethod {
108 PixelEdge,
111 MooreNeighbor,
114}
115
116impl Default for BoundaryMethod {
117 fn default() -> Self {
118 Self::PixelEdge
119 }
120}
121
122impl Default for PolygonizeOptions {
123 fn default() -> Self {
124 Self {
125 connectivity: Connectivity::default(),
126 nodata: None,
127 nodata_tolerance: 1e-10,
128 transform: None,
129 simplify_tolerance: 0.0,
130 min_area: 0.0,
131 boundary_method: BoundaryMethod::default(),
132 }
133 }
134}
135
136#[derive(Debug, Clone)]
138pub struct PolygonFeature {
139 pub value: f64,
141 pub polygon: Polygon,
143}
144
145#[derive(Debug, Clone)]
147pub struct PolygonizeResult {
148 pub polygons: Vec<PolygonFeature>,
150 pub num_components: usize,
152 pub width: usize,
154 pub height: usize,
156}
157
158impl PolygonizeResult {
159 pub fn polygons_for_value(&self, value: f64, tolerance: f64) -> Vec<&PolygonFeature> {
161 self.polygons
162 .iter()
163 .filter(|f| (f.value - value).abs() <= tolerance)
164 .collect()
165 }
166
167 pub fn to_multipolygon(&self) -> MultiPolygon {
169 let polys: Vec<Polygon> = self.polygons.iter().map(|f| f.polygon.clone()).collect();
170 MultiPolygon::new(polys)
171 }
172
173 pub fn unique_values(&self) -> Vec<f64> {
175 let mut values: Vec<f64> = Vec::new();
176 for feat in &self.polygons {
177 if !values
178 .iter()
179 .any(|v| (*v - feat.value).abs() < f64::EPSILON)
180 {
181 values.push(feat.value);
182 }
183 }
184 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
185 values
186 }
187}
188
189fn connected_component_label(
199 grid: &[f64],
200 width: usize,
201 height: usize,
202 connectivity: Connectivity,
203 nodata: Option<f64>,
204 nodata_tolerance: f64,
205) -> Result<(Vec<u32>, u32, HashMap<u32, f64>)> {
206 let n = width * height;
207 if grid.len() != n {
208 return Err(AlgorithmError::InvalidInput(
209 "grid size does not match width*height".to_string(),
210 ));
211 }
212
213 let mut labels = vec![0u32; n];
214 let mut next_label = 1u32;
215 let mut uf = UnionFind::with_capacity(256); let mut label_values: HashMap<u32, f64> = HashMap::new();
217
218 for y in 0..height {
220 for x in 0..width {
221 let idx = y * width + x;
222 let val = grid[idx];
223
224 if is_nodata_value(val, nodata, nodata_tolerance) {
226 continue;
227 }
228
229 let neighbor_labels = get_neighbor_labels(
231 grid,
232 &labels,
233 width,
234 height,
235 x,
236 y,
237 val,
238 connectivity,
239 nodata,
240 nodata_tolerance,
241 );
242
243 if neighbor_labels.is_empty() {
244 uf.ensure_label(next_label);
246 labels[idx] = next_label;
247 label_values.insert(next_label, val);
248 next_label = next_label.saturating_add(1);
249 } else {
250 let mut min_label = neighbor_labels[0];
252 for &nl in &neighbor_labels[1..] {
253 if nl < min_label {
254 min_label = nl;
255 }
256 }
257 labels[idx] = min_label;
258
259 for &nl in &neighbor_labels {
261 if nl != min_label {
262 uf.ensure_label(nl.max(min_label));
263 uf.union(min_label, nl);
264 }
265 }
266 }
267 }
268 }
269
270 let mut canonical_map: HashMap<u32, u32> = HashMap::new();
272 let mut remap_next = 1u32;
273
274 for label in labels.iter_mut() {
275 if *label == 0 {
276 continue;
277 }
278 let root = uf.find(*label);
279 let canonical = *canonical_map.entry(root).or_insert_with(|| {
280 let c = remap_next;
281 remap_next = remap_next.saturating_add(1);
282 c
283 });
284 *label = canonical;
285 }
286
287 let mut new_label_values: HashMap<u32, f64> = HashMap::new();
289 for (&orig_label, &val) in &label_values {
290 let root = uf.find(orig_label);
291 if let Some(&canonical) = canonical_map.get(&root) {
292 new_label_values.entry(canonical).or_insert(val);
293 }
294 }
295
296 let num_components = remap_next.saturating_sub(1);
297 Ok((labels, num_components, new_label_values))
298}
299
300#[inline]
302fn is_nodata_value(value: f64, nodata: Option<f64>, tolerance: f64) -> bool {
303 if value.is_nan() {
304 return true;
305 }
306 if let Some(nd) = nodata {
307 (value - nd).abs() <= tolerance
308 } else {
309 false
310 }
311}
312
313#[inline]
315fn same_value(a: f64, b: f64) -> bool {
316 (a - b).abs() < f64::EPSILON * 100.0 || (a == b)
318}
319
320fn get_neighbor_labels(
322 grid: &[f64],
323 labels: &[u32],
324 width: usize,
325 height: usize,
326 x: usize,
327 y: usize,
328 value: f64,
329 connectivity: Connectivity,
330 nodata: Option<f64>,
331 nodata_tolerance: f64,
332) -> Vec<u32> {
333 let mut result = Vec::with_capacity(4);
334
335 if y > 0 {
341 let nidx = (y - 1) * width + x;
342 let nl = labels[nidx];
343 let nv = grid[nidx];
344 if nl != 0 && !is_nodata_value(nv, nodata, nodata_tolerance) && same_value(value, nv) {
345 result.push(nl);
346 }
347 }
348
349 if x > 0 {
351 let nidx = y * width + (x - 1);
352 let nl = labels[nidx];
353 let nv = grid[nidx];
354 if nl != 0 && !is_nodata_value(nv, nodata, nodata_tolerance) && same_value(value, nv) {
355 if !result.contains(&nl) {
356 result.push(nl);
357 }
358 }
359 }
360
361 if connectivity == Connectivity::Eight {
362 if y > 0 && x > 0 {
364 let nidx = (y - 1) * width + (x - 1);
365 let nl = labels[nidx];
366 let nv = grid[nidx];
367 if nl != 0
368 && !is_nodata_value(nv, nodata, nodata_tolerance)
369 && same_value(value, nv)
370 && !result.contains(&nl)
371 {
372 result.push(nl);
373 }
374 }
375
376 if y > 0 && x + 1 < width {
378 let nidx = (y - 1) * width + (x + 1);
379 let nl = labels[nidx];
380 let nv = grid[nidx];
381 if nl != 0
382 && !is_nodata_value(nv, nodata, nodata_tolerance)
383 && same_value(value, nv)
384 && !result.contains(&nl)
385 {
386 result.push(nl);
387 }
388 }
389 }
390
391 result
392}
393
394fn simplify_ring(coords: &[(f64, f64)], tolerance: f64) -> Vec<(f64, f64)> {
400 if coords.len() <= 4 || tolerance <= 0.0 {
401 return coords.to_vec();
402 }
403 let tol_sq = tolerance * tolerance;
404 let mut keep = vec![false; coords.len()];
405 keep[0] = true;
406 keep[coords.len() - 1] = true;
407 dp_simplify(coords, 0, coords.len() - 1, tol_sq, &mut keep);
408
409 let result: Vec<(f64, f64)> = coords
410 .iter()
411 .enumerate()
412 .filter(|(i, _)| keep[*i])
413 .map(|(_, c)| *c)
414 .collect();
415
416 if result.len() >= 3 {
418 result
419 } else {
420 coords.to_vec() }
422}
423
424fn dp_simplify(coords: &[(f64, f64)], start: usize, end: usize, tol_sq: f64, keep: &mut [bool]) {
426 if end <= start + 1 {
427 return;
428 }
429 let (sx, sy) = coords[start];
430 let (ex, ey) = coords[end];
431 let dx = ex - sx;
432 let dy = ey - sy;
433 let len_sq = dx.mul_add(dx, dy * dy);
434
435 let mut max_dist_sq = 0.0_f64;
436 let mut max_idx = start;
437
438 for i in (start + 1)..end {
439 let (px, py) = coords[i];
440 let dist_sq = if len_sq < f64::EPSILON {
441 let dpx = px - sx;
442 let dpy = py - sy;
443 dpx.mul_add(dpx, dpy * dpy)
444 } else {
445 let t = ((px - sx).mul_add(dx, (py - sy) * dy) / len_sq).clamp(0.0, 1.0);
446 let proj_x = t.mul_add(dx, sx);
447 let proj_y = t.mul_add(dy, sy);
448 let dpx = px - proj_x;
449 let dpy = py - proj_y;
450 dpx.mul_add(dpx, dpy * dpy)
451 };
452
453 if dist_sq > max_dist_sq {
454 max_dist_sq = dist_sq;
455 max_idx = i;
456 }
457 }
458
459 if max_dist_sq > tol_sq {
460 keep[max_idx] = true;
461 dp_simplify(coords, start, max_idx, tol_sq, keep);
462 dp_simplify(coords, max_idx, end, tol_sq, keep);
463 }
464}
465
466pub fn polygonize(
505 grid: &[f64],
506 width: usize,
507 height: usize,
508 options: &PolygonizeOptions,
509) -> Result<PolygonizeResult> {
510 if width == 0 || height == 0 {
512 return Err(AlgorithmError::InvalidDimensions {
513 message: "grid dimensions must be positive",
514 actual: width.min(height),
515 expected: 1,
516 });
517 }
518 if grid.len() != width * height {
519 return Err(AlgorithmError::InvalidInput(format!(
520 "grid size ({}) does not match width*height ({}*{}={})",
521 grid.len(),
522 width,
523 height,
524 width * height
525 )));
526 }
527
528 let (labels, num_components, label_values) = connected_component_label(
530 grid,
531 width,
532 height,
533 options.connectivity,
534 options.nodata,
535 options.nodata_tolerance,
536 )?;
537
538 if num_components == 0 {
539 return Ok(PolygonizeResult {
540 polygons: Vec::new(),
541 num_components: 0,
542 width,
543 height,
544 });
545 }
546
547 let classified_polys = match options.boundary_method {
549 BoundaryMethod::PixelEdge => extract_pixel_edge_boundaries(&labels, width, height)?,
550 BoundaryMethod::MooreNeighbor => {
551 trace_boundaries(&labels, width, height, options.connectivity)?
552 }
553 };
554
555 let mut features = Vec::with_capacity(classified_polys.len());
557
558 for cpoly in classified_polys {
559 let value = label_values.get(&cpoly.label).copied().unwrap_or(0.0);
561
562 let exterior_coords = if options.simplify_tolerance > 0.0 {
564 simplify_ring(&cpoly.exterior, options.simplify_tolerance)
565 } else {
566 cpoly.exterior.clone()
567 };
568
569 let hole_coords: Vec<Vec<(f64, f64)>> = if options.simplify_tolerance > 0.0 {
570 cpoly
571 .holes
572 .iter()
573 .map(|h| simplify_ring(h, options.simplify_tolerance))
574 .collect()
575 } else {
576 cpoly.holes.clone()
577 };
578
579 let area = boundary::compute_signed_area(&exterior_coords).abs();
581 if options.min_area > 0.0 && area < options.min_area {
582 continue;
583 }
584
585 let ext_world_coords = match &options.transform {
587 Some(gt) => transform_coords(&exterior_coords, gt),
588 None => pixel_coords_to_coordinates(&exterior_coords),
589 };
590
591 let hole_world_coords: Vec<Vec<Coordinate>> = hole_coords
592 .iter()
593 .map(|h| match &options.transform {
594 Some(gt) => transform_coords(h, gt),
595 None => pixel_coords_to_coordinates(h),
596 })
597 .collect();
598
599 let polygon = build_polygon(ext_world_coords, hole_world_coords)?;
601
602 if let Some(poly) = polygon {
603 features.push(PolygonFeature {
604 value,
605 polygon: poly,
606 });
607 }
608 }
609
610 Ok(PolygonizeResult {
611 polygons: features,
612 num_components: num_components as usize,
613 width,
614 height,
615 })
616}
617
618pub fn polygonize_raster(
627 raster: &oxigdal_core::buffer::RasterBuffer,
628 options: &PolygonizeOptions,
629) -> Result<PolygonizeResult> {
630 let width = raster.width() as usize;
631 let height = raster.height() as usize;
632
633 let mut grid = vec![0.0_f64; width * height];
634 for y in 0..height {
635 for x in 0..width {
636 grid[y * width + x] = raster
637 .get_pixel(x as u64, y as u64)
638 .map_err(AlgorithmError::Core)?;
639 }
640 }
641
642 polygonize(&grid, width, height, options)
643}
644
645fn build_polygon(
647 exterior: Vec<Coordinate>,
648 holes: Vec<Vec<Coordinate>>,
649) -> Result<Option<Polygon>> {
650 if exterior.len() < 4 {
651 return Ok(None);
652 }
653
654 let mut ext = exterior;
656 let first = ext[0];
657 let last = ext[ext.len() - 1];
658 if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
659 ext.push(first);
660 }
661
662 if ext.len() < 4 {
663 return Ok(None);
664 }
665
666 let ext_ring = LineString::new(ext).map_err(|e| AlgorithmError::Core(e))?;
667
668 let mut interior_rings = Vec::new();
670 for hole in holes {
671 if hole.len() < 4 {
672 continue;
673 }
674 let mut h = hole;
675 let hfirst = h[0];
676 let hlast = h[h.len() - 1];
677 if (hfirst.x - hlast.x).abs() > f64::EPSILON || (hfirst.y - hlast.y).abs() > f64::EPSILON {
678 h.push(hfirst);
679 }
680 if h.len() >= 4 {
681 if let Ok(ring) = LineString::new(h) {
682 interior_rings.push(ring);
683 }
684 }
685 }
686
687 match Polygon::new(ext_ring, interior_rings) {
688 Ok(poly) => Ok(Some(poly)),
689 Err(_) => Ok(None), }
691}
692
693#[cfg(test)]
698mod tests {
699 use super::*;
700 use oxigdal_core::buffer::RasterBuffer;
701 use oxigdal_core::types::RasterDataType;
702
703 fn make_raster(width: usize, height: usize, values: &[f64]) -> RasterBuffer {
705 let mut buf = RasterBuffer::zeros(width as u64, height as u64, RasterDataType::Float64);
706 for row in 0..height {
707 for col in 0..width {
708 let _ = buf.set_pixel(col as u64, row as u64, values[row * width + col]);
709 }
710 }
711 buf
712 }
713
714 #[test]
717 fn test_ccl_single_value() {
718 let grid = vec![1.0; 9];
719 let (labels, num, values) =
720 connected_component_label(&grid, 3, 3, Connectivity::Eight, None, 1e-10)
721 .expect("CCL should succeed");
722 assert_eq!(num, 1);
723 assert!(values.values().any(|&v| (v - 1.0).abs() < f64::EPSILON));
724 let first_label = labels[0];
726 assert!(first_label > 0);
727 for &l in &labels {
728 assert_eq!(l, first_label);
729 }
730 }
731
732 #[test]
733 fn test_ccl_two_regions() {
734 #[rustfmt::skip]
735 let grid = vec![
736 1.0, 1.0, 2.0,
737 1.0, 1.0, 2.0,
738 2.0, 2.0, 2.0,
739 ];
740 let (labels, num, _) =
741 connected_component_label(&grid, 3, 3, Connectivity::Four, None, 1e-10)
742 .expect("CCL should succeed");
743 assert_eq!(num, 2);
744 assert_ne!(labels[0], labels[2]);
746 }
747
748 #[test]
749 fn test_ccl_nodata() {
750 let grid = vec![1.0, f64::NAN, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
751 let (labels, _, _) =
752 connected_component_label(&grid, 3, 3, Connectivity::Four, None, 1e-10)
753 .expect("CCL should succeed");
754 assert_eq!(labels[1], 0);
756 }
757
758 #[test]
759 fn test_ccl_custom_nodata() {
760 let grid = vec![1.0, -9999.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
761 let (labels, _, _) =
762 connected_component_label(&grid, 3, 3, Connectivity::Four, Some(-9999.0), 1e-10)
763 .expect("CCL should succeed");
764 assert_eq!(labels[1], 0);
765 }
766
767 #[test]
768 fn test_ccl_four_vs_eight_connectivity() {
769 #[rustfmt::skip]
771 let grid = vec![
772 1.0, 0.0,
773 0.0, 1.0,
774 ];
775
776 let (_, num_4, _) =
777 connected_component_label(&grid, 2, 2, Connectivity::Four, Some(0.0), 1e-10)
778 .expect("CCL should succeed");
779 let (_, num_8, _) =
780 connected_component_label(&grid, 2, 2, Connectivity::Eight, Some(0.0), 1e-10)
781 .expect("CCL should succeed");
782
783 assert_eq!(num_4, 2);
785 assert_eq!(num_8, 1);
786 }
787
788 #[test]
789 fn test_ccl_empty_grid() {
790 let grid = vec![f64::NAN; 4];
791 let (labels, num, _) =
792 connected_component_label(&grid, 2, 2, Connectivity::Eight, None, 1e-10)
793 .expect("CCL should succeed");
794 assert_eq!(num, 0);
795 for &l in &labels {
796 assert_eq!(l, 0);
797 }
798 }
799
800 #[test]
801 fn test_ccl_size_mismatch() {
802 let grid = vec![1.0; 5];
803 let result = connected_component_label(&grid, 2, 2, Connectivity::Eight, None, 1e-10);
804 assert!(result.is_err());
805 }
806
807 #[test]
810 fn test_polygonize_basic() {
811 #[rustfmt::skip]
812 let grid = vec![
813 1.0, 1.0, 2.0, 2.0,
814 1.0, 1.0, 2.0, 2.0,
815 3.0, 3.0, 2.0, 2.0,
816 3.0, 3.0, 3.0, 3.0,
817 ];
818 let opts = PolygonizeOptions::default();
819 let result = polygonize(&grid, 4, 4, &opts);
820 assert!(result.is_ok());
821 let result = result.expect("polygonize should succeed");
822 assert_eq!(result.num_components, 3);
823 assert_eq!(result.polygons.len(), 3);
824 assert_eq!(result.width, 4);
825 assert_eq!(result.height, 4);
826 }
827
828 #[test]
829 fn test_polygonize_single_value() {
830 let grid = vec![5.0; 16];
831 let opts = PolygonizeOptions::default();
832 let result = polygonize(&grid, 4, 4, &opts);
833 assert!(result.is_ok());
834 let result = result.expect("polygonize should succeed");
835 assert_eq!(result.num_components, 1);
836 assert_eq!(result.polygons.len(), 1);
837 assert!((result.polygons[0].value - 5.0).abs() < f64::EPSILON);
838 }
839
840 #[test]
841 fn test_polygonize_with_nodata() {
842 #[rustfmt::skip]
843 let grid = vec![
844 1.0, 1.0, 0.0,
845 1.0, 0.0, 2.0,
846 0.0, 2.0, 2.0,
847 ];
848 let mut opts = PolygonizeOptions::default();
849 opts.nodata = Some(0.0);
850 let result = polygonize(&grid, 3, 3, &opts);
851 assert!(result.is_ok());
852 let result = result.expect("polygonize should succeed");
853 assert!(result.num_components >= 2);
855 for feat in &result.polygons {
857 assert!(
858 (feat.value).abs() > f64::EPSILON,
859 "nodata polygons should be excluded"
860 );
861 }
862 }
863
864 #[test]
865 fn test_polygonize_with_transform() {
866 let grid = vec![1.0; 4];
867 let mut opts = PolygonizeOptions::default();
868 opts.transform = Some(GeoTransform::north_up(100.0, 200.0, 10.0, -10.0));
869 let result = polygonize(&grid, 2, 2, &opts);
870 assert!(result.is_ok());
871 let result = result.expect("polygonize should succeed");
872 assert_eq!(result.polygons.len(), 1);
873
874 let poly = &result.polygons[0].polygon;
876 for coord in &poly.exterior.coords {
877 assert!(
879 coord.x >= 99.0,
880 "world x should be near origin, got {}",
881 coord.x
882 );
883 }
884 }
885
886 #[test]
887 fn test_polygonize_min_area_filter() {
888 #[rustfmt::skip]
889 let grid = vec![
890 1.0, 1.0, 1.0, 1.0,
891 1.0, 1.0, 1.0, 1.0,
892 1.0, 1.0, 1.0, 2.0,
893 1.0, 1.0, 1.0, 1.0,
894 ];
895 let mut opts = PolygonizeOptions::default();
896 opts.min_area = 2.0; let result = polygonize(&grid, 4, 4, &opts);
898 assert!(result.is_ok());
899 let result = result.expect("polygonize should succeed");
900 for feat in &result.polygons {
902 assert!(
903 (feat.value - 2.0).abs() > f64::EPSILON,
904 "small region should be filtered"
905 );
906 }
907 }
908
909 #[test]
910 fn test_polygonize_simplify() {
911 let grid = vec![1.0; 100]; let mut opts = PolygonizeOptions::default();
913 opts.simplify_tolerance = 2.0;
914 let result = polygonize(&grid, 10, 10, &opts);
915 assert!(result.is_ok());
916 let result = result.expect("polygonize should succeed");
917 assert!(!result.polygons.is_empty());
918 }
919
920 #[test]
921 fn test_polygonize_zero_dimensions() {
922 let result = polygonize(&[], 0, 0, &PolygonizeOptions::default());
923 assert!(result.is_err());
924 }
925
926 #[test]
927 fn test_polygonize_size_mismatch() {
928 let result = polygonize(&[1.0; 5], 2, 2, &PolygonizeOptions::default());
929 assert!(result.is_err());
930 }
931
932 #[test]
933 fn test_polygonize_all_nodata() {
934 let grid = vec![f64::NAN; 9];
935 let result = polygonize(&grid, 3, 3, &PolygonizeOptions::default());
936 assert!(result.is_ok());
937 let result = result.expect("polygonize should succeed");
938 assert_eq!(result.num_components, 0);
939 assert!(result.polygons.is_empty());
940 }
941
942 #[test]
943 fn test_polygonize_moore_neighbor_method() {
944 let grid = vec![1.0; 9];
945 let mut opts = PolygonizeOptions::default();
946 opts.boundary_method = BoundaryMethod::MooreNeighbor;
947 let result = polygonize(&grid, 3, 3, &opts);
948 assert!(result.is_ok());
949 let result = result.expect("polygonize should succeed");
950 assert!(!result.polygons.is_empty());
951 }
952
953 #[test]
954 fn test_polygonize_four_connectivity() {
955 let grid = vec![1.0; 9];
956 let mut opts = PolygonizeOptions::default();
957 opts.connectivity = Connectivity::Four;
958 let result = polygonize(&grid, 3, 3, &opts);
959 assert!(result.is_ok());
960 let result = result.expect("polygonize should succeed");
961 assert_eq!(result.num_components, 1);
962 }
963
964 #[test]
967 fn test_polygonize_result_for_value() {
968 #[rustfmt::skip]
969 let grid = vec![
970 1.0, 1.0, 2.0,
971 1.0, 1.0, 2.0,
972 2.0, 2.0, 2.0,
973 ];
974 let result = polygonize(&grid, 3, 3, &PolygonizeOptions::default());
975 assert!(result.is_ok());
976 let result = result.expect("polygonize should succeed");
977
978 let ones = result.polygons_for_value(1.0, f64::EPSILON);
979 assert_eq!(ones.len(), 1);
980 let twos = result.polygons_for_value(2.0, f64::EPSILON);
981 assert_eq!(twos.len(), 1);
982 let threes = result.polygons_for_value(3.0, f64::EPSILON);
983 assert!(threes.is_empty());
984 }
985
986 #[test]
987 fn test_polygonize_result_to_multipolygon() {
988 let grid = vec![1.0; 4];
989 let result = polygonize(&grid, 2, 2, &PolygonizeOptions::default());
990 assert!(result.is_ok());
991 let result = result.expect("polygonize should succeed");
992 let mp = result.to_multipolygon();
993 assert_eq!(mp.polygons.len(), 1);
994 }
995
996 #[test]
997 fn test_polygonize_result_unique_values() {
998 #[rustfmt::skip]
999 let grid = vec![
1000 1.0, 2.0,
1001 3.0, 4.0,
1002 ];
1003 let result = polygonize(&grid, 2, 2, &PolygonizeOptions::default());
1004 assert!(result.is_ok());
1005 let result = result.expect("polygonize should succeed");
1006 let values = result.unique_values();
1007 assert_eq!(values.len(), 4);
1008 }
1009
1010 #[test]
1013 fn test_polygonize_raster_buffer() {
1014 let raster = make_raster(3, 3, &[1.0; 9]);
1015 let result = polygonize_raster(&raster, &PolygonizeOptions::default());
1016 assert!(result.is_ok());
1017 let result = result.expect("polygonize_raster should succeed");
1018 assert_eq!(result.num_components, 1);
1019 assert_eq!(result.polygons.len(), 1);
1020 }
1021
1022 #[test]
1023 fn test_polygonize_raster_with_mixed_values() {
1024 #[rustfmt::skip]
1025 let values = vec![
1026 1.0, 1.0, 2.0, 2.0,
1027 1.0, 1.0, 2.0, 2.0,
1028 3.0, 3.0, 3.0, 3.0,
1029 ];
1030 let raster = make_raster(4, 3, &values);
1031 let result = polygonize_raster(&raster, &PolygonizeOptions::default());
1032 assert!(result.is_ok());
1033 let result = result.expect("polygonize_raster should succeed");
1034 assert_eq!(result.num_components, 3);
1035 }
1036
1037 #[test]
1040 fn test_polygonize_checkerboard_4conn() {
1041 #[rustfmt::skip]
1043 let grid = vec![
1044 1.0, 2.0, 1.0,
1045 2.0, 1.0, 2.0,
1046 1.0, 2.0, 1.0,
1047 ];
1048 let mut opts = PolygonizeOptions::default();
1049 opts.connectivity = Connectivity::Four;
1050 let result = polygonize(&grid, 3, 3, &opts);
1051 assert!(result.is_ok());
1052 let result = result.expect("polygonize should succeed");
1053 assert_eq!(result.num_components, 9);
1056 }
1057
1058 #[test]
1059 fn test_polygonize_checkerboard_8conn() {
1060 #[rustfmt::skip]
1062 let grid = vec![
1063 1.0, 2.0, 1.0,
1064 2.0, 1.0, 2.0,
1065 1.0, 2.0, 1.0,
1066 ];
1067 let mut opts = PolygonizeOptions::default();
1068 opts.connectivity = Connectivity::Eight;
1069 let result = polygonize(&grid, 3, 3, &opts);
1070 assert!(result.is_ok());
1071 let result = result.expect("polygonize should succeed");
1072 assert_eq!(result.num_components, 2);
1074 }
1075
1076 #[test]
1079 fn test_polygonize_large_grid() {
1080 let width = 50;
1082 let height = 50;
1083 let mut grid = vec![0.0_f64; width * height];
1084 let cx = 25.0_f64;
1085 let cy = 25.0_f64;
1086 for y in 0..height {
1087 for x in 0..width {
1088 let dx = x as f64 - cx;
1089 let dy = y as f64 - cy;
1090 let dist = (dx.mul_add(dx, dy * dy)).sqrt();
1091 grid[y * width + x] = (dist / 5.0).floor();
1093 }
1094 }
1095 let result = polygonize(&grid, width, height, &PolygonizeOptions::default());
1096 assert!(result.is_ok());
1097 let result = result.expect("polygonize should succeed");
1098 assert!(result.num_components > 1);
1099 assert!(!result.polygons.is_empty());
1100 }
1101
1102 #[test]
1103 fn test_polygonize_stress_500x500() {
1104 let width = 500;
1109 let height = 500;
1110 let mut grid = vec![0.0_f64; width * height];
1111 for y in 0..height {
1112 for x in 0..width {
1113 let band = (y / 100) as f64;
1115 let stripe = (x / 125) as f64;
1116 grid[y * width + x] = band * 4.0 + stripe;
1117 }
1118 }
1119 let result = polygonize(&grid, width, height, &PolygonizeOptions::default());
1120 assert!(result.is_ok());
1121 let result = result.expect("polygonize should succeed");
1122 assert!(result.num_components >= 5);
1124 assert!(result.num_components <= 64);
1125 assert!(!result.polygons.is_empty());
1126 assert_eq!(result.width, 500);
1127 assert_eq!(result.height, 500);
1128 }
1129
1130 #[test]
1133 fn test_polygonize_donut_with_hole() {
1134 #[rustfmt::skip]
1136 let grid = vec![
1137 1.0, 1.0, 1.0, 1.0, 1.0,
1138 1.0, 0.0, 0.0, 0.0, 1.0,
1139 1.0, 0.0, 0.0, 0.0, 1.0,
1140 1.0, 0.0, 0.0, 0.0, 1.0,
1141 1.0, 1.0, 1.0, 1.0, 1.0,
1142 ];
1143 let mut opts = PolygonizeOptions::default();
1144 opts.nodata = Some(0.0);
1145 let result = polygonize(&grid, 5, 5, &opts);
1146 assert!(result.is_ok());
1147 let result = result.expect("polygonize should succeed");
1148 assert_eq!(result.polygons.len(), 1);
1150 let feat = &result.polygons[0];
1151 assert!((feat.value - 1.0).abs() < f64::EPSILON);
1152 assert_eq!(
1153 feat.polygon.interiors.len(),
1154 1,
1155 "donut polygon should have exactly one interior hole"
1156 );
1157 }
1158
1159 #[test]
1160 fn test_polygonize_multiple_disjoint_components() {
1161 #[rustfmt::skip]
1163 let grid = vec![
1164 1.0, 1.0, 0.0, 1.0, 1.0,
1165 ];
1166 let mut opts = PolygonizeOptions::default();
1167 opts.nodata = Some(0.0);
1168 let result = polygonize(&grid, 5, 1, &opts);
1169 assert!(result.is_ok());
1170 let result = result.expect("polygonize should succeed");
1171 assert_eq!(result.num_components, 2);
1173 }
1174
1175 #[test]
1178 fn test_options_default() {
1179 let opts = PolygonizeOptions::default();
1180 assert_eq!(opts.connectivity, Connectivity::Eight);
1181 assert!(opts.nodata.is_none());
1182 assert!((opts.nodata_tolerance - 1e-10).abs() < f64::EPSILON);
1183 assert!(opts.transform.is_none());
1184 assert!((opts.simplify_tolerance).abs() < f64::EPSILON);
1185 assert!((opts.min_area).abs() < f64::EPSILON);
1186 assert_eq!(opts.boundary_method, BoundaryMethod::PixelEdge);
1187 }
1188
1189 #[test]
1190 fn test_boundary_method_default() {
1191 assert_eq!(BoundaryMethod::default(), BoundaryMethod::PixelEdge);
1192 }
1193
1194 #[test]
1197 fn test_polygonize_1x1_grid() {
1198 let grid = vec![1.0];
1199 let result = polygonize(&grid, 1, 1, &PolygonizeOptions::default());
1200 assert!(result.is_ok());
1201 let result = result.expect("polygonize should succeed");
1202 assert_eq!(result.num_components, 1);
1203 }
1204
1205 #[test]
1206 fn test_polygonize_1xn_grid() {
1207 let grid = vec![1.0, 2.0, 1.0, 2.0];
1208 let result = polygonize(&grid, 4, 1, &PolygonizeOptions::default());
1209 assert!(result.is_ok());
1210 }
1211
1212 #[test]
1213 fn test_polygonize_nx1_grid() {
1214 let grid = vec![1.0, 2.0, 1.0, 2.0];
1215 let result = polygonize(&grid, 1, 4, &PolygonizeOptions::default());
1216 assert!(result.is_ok());
1217 }
1218
1219 #[test]
1220 fn test_polygonize_with_negative_values() {
1221 let grid = vec![-1.0, -2.0, -1.0, -2.0];
1222 let result = polygonize(&grid, 2, 2, &PolygonizeOptions::default());
1223 assert!(result.is_ok());
1224 }
1225
1226 #[test]
1227 fn test_same_value_function() {
1228 assert!(same_value(1.0, 1.0));
1229 assert!(same_value(0.0, 0.0));
1230 assert!(!same_value(1.0, 2.0));
1231 }
1232
1233 #[test]
1234 fn test_is_nodata_value() {
1235 assert!(is_nodata_value(f64::NAN, None, 1e-10));
1236 assert!(is_nodata_value(-9999.0, Some(-9999.0), 1e-10));
1237 assert!(!is_nodata_value(1.0, Some(-9999.0), 1e-10));
1238 assert!(!is_nodata_value(1.0, None, 1e-10));
1239 }
1240
1241 #[test]
1242 fn test_simplify_ring_passthrough() {
1243 let ring = vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)];
1244 let result = simplify_ring(&ring, 0.0);
1245 assert_eq!(result.len(), ring.len());
1246 }
1247
1248 #[test]
1249 fn test_simplify_ring_with_tolerance() {
1250 let ring = vec![
1251 (0.0, 0.0),
1252 (0.5, 0.001),
1253 (1.0, 0.0),
1254 (1.0, 0.5),
1255 (1.0, 1.0),
1256 (0.5, 1.001),
1257 (0.0, 1.0),
1258 (0.0, 0.5),
1259 (0.0, 0.0),
1260 ];
1261 let result = simplify_ring(&ring, 0.1);
1262 assert!(result.len() <= ring.len());
1263 }
1264
1265 #[test]
1266 fn test_build_polygon_valid() {
1267 let ext = vec![
1268 Coordinate::new_2d(0.0, 0.0),
1269 Coordinate::new_2d(1.0, 0.0),
1270 Coordinate::new_2d(1.0, 1.0),
1271 Coordinate::new_2d(0.0, 1.0),
1272 Coordinate::new_2d(0.0, 0.0),
1273 ];
1274 let result = build_polygon(ext, vec![]);
1275 assert!(result.is_ok());
1276 assert!(result.expect("should succeed").is_some());
1277 }
1278
1279 #[test]
1280 fn test_build_polygon_too_few_coords() {
1281 let ext = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(1.0, 0.0)];
1282 let result = build_polygon(ext, vec![]);
1283 assert!(result.is_ok());
1284 assert!(result.expect("should succeed").is_none());
1285 }
1286
1287 #[test]
1288 fn test_build_polygon_auto_close() {
1289 let ext = vec![
1290 Coordinate::new_2d(0.0, 0.0),
1291 Coordinate::new_2d(1.0, 0.0),
1292 Coordinate::new_2d(1.0, 1.0),
1293 Coordinate::new_2d(0.0, 1.0),
1294 ];
1296 let result = build_polygon(ext, vec![]);
1297 assert!(result.is_ok());
1298 let poly = result.expect("should succeed");
1300 assert!(poly.is_some());
1301 }
1302
1303 #[test]
1306 fn test_pixel_edge_produces_valid_polygons() {
1307 #[rustfmt::skip]
1308 let grid = vec![
1309 1.0, 1.0, 2.0,
1310 1.0, 2.0, 2.0,
1311 2.0, 2.0, 2.0,
1312 ];
1313 let result = polygonize(&grid, 3, 3, &PolygonizeOptions::default());
1314 assert!(result.is_ok());
1315 let result = result.expect("polygonize should succeed");
1316 for feat in &result.polygons {
1317 assert!(
1319 feat.polygon.exterior.coords.len() >= 4,
1320 "polygon for value {} has too few coords: {}",
1321 feat.value,
1322 feat.polygon.exterior.coords.len()
1323 );
1324 }
1325 }
1326}