1use std::path::Path;
4use std::sync::atomic::{AtomicUsize, Ordering};
5use rayon::prelude::*;
6use wbprojection::Crs;
7use wide::{f64x4, CmpNe};
8
9use crate::error::{Result, RasterError};
10use crate::formats::RasterFormat;
11use crate::crs_info::CrsInfo;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum DataType {
18 U8,
20 I8,
22 U16,
24 I16,
26 U32,
28 I32,
30 U64,
32 I64,
34 #[default]
36 F32,
37 F64,
39}
40
41impl DataType {
42 pub fn size_bytes(self) -> usize {
44 match self {
45 DataType::U8 | DataType::I8 => 1,
46 DataType::U16 | DataType::I16 => 2,
47 DataType::U32 | DataType::I32 | DataType::F32 => 4,
48 DataType::U64 | DataType::I64 | DataType::F64 => 8,
49 }
50 }
51
52 pub fn as_str(self) -> &'static str {
54 match self {
55 DataType::U8 => "uint8",
56 DataType::I8 => "int8",
57 DataType::U16 => "uint16",
58 DataType::I16 => "int16",
59 DataType::U32 => "uint32",
60 DataType::I32 => "int32",
61 DataType::U64 => "uint64",
62 DataType::I64 => "int64",
63 DataType::F32 => "float32",
64 DataType::F64 => "float64",
65 }
66 }
67
68 #[allow(clippy::should_implement_trait)]
70 pub fn from_str(s: &str) -> Option<Self> {
71 match s.trim().to_ascii_lowercase().as_str() {
72 "uint8" | "u8" | "byte" => Some(DataType::U8),
73 "int8" | "i8" => Some(DataType::I8),
74 "uint16" | "u16" => Some(DataType::U16),
75 "int16" | "i16" | "integer" | "short" => Some(DataType::I16),
76 "uint32" | "u32" => Some(DataType::U32),
77 "int32" | "i32" | "long" => Some(DataType::I32),
78 "uint64" | "u64" => Some(DataType::U64),
79 "int64" | "i64" | "longlong" => Some(DataType::I64),
80 "float32" | "f32" | "float" | "real" => Some(DataType::F32),
81 "float64" | "f64" | "double" => Some(DataType::F64),
82 _ => None,
83 }
84 }
85}
86
87impl std::fmt::Display for DataType {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.write_str(self.as_str())
90 }
91}
92
93#[derive(Debug, Clone)]
95pub enum RasterData {
96 U8(Vec<u8>),
98 I8(Vec<i8>),
100 U16(Vec<u16>),
102 I16(Vec<i16>),
104 U32(Vec<u32>),
106 I32(Vec<i32>),
108 U64(Vec<u64>),
110 I64(Vec<i64>),
112 F32(Vec<f32>),
114 F64(Vec<f64>),
116}
117
118pub enum RasterRowMut<'a> {
120 U8(&'a mut [u8]),
122 I8(&'a mut [i8]),
124 U16(&'a mut [u16]),
126 I16(&'a mut [i16]),
128 U32(&'a mut [u32]),
130 I32(&'a mut [i32]),
132 U64(&'a mut [u64]),
134 I64(&'a mut [i64]),
136 F32(&'a mut [f32]),
138 F64(&'a mut [f64]),
140}
141
142pub enum RasterRowRef<'a> {
144 U8(&'a [u8]),
146 I8(&'a [i8]),
148 U16(&'a [u16]),
150 I16(&'a [i16]),
152 U32(&'a [u32]),
154 I32(&'a [i32]),
156 U64(&'a [u64]),
158 I64(&'a [i64]),
160 F32(&'a [f32]),
162 F64(&'a [f64]),
164}
165
166impl RasterData {
167 pub fn new_filled(data_type: DataType, len: usize, value: f64) -> Self {
170 match data_type {
171 DataType::U8 => Self::U8(vec![value as u8; len]),
172 DataType::I8 => Self::I8(vec![value as i8; len]),
173 DataType::U16 => Self::U16(vec![value as u16; len]),
174 DataType::I16 => Self::I16(vec![value as i16; len]),
175 DataType::U32 => Self::U32(vec![value as u32; len]),
176 DataType::I32 => Self::I32(vec![value as i32; len]),
177 DataType::U64 => Self::U64(vec![value as u64; len]),
178 DataType::I64 => Self::I64(vec![value as i64; len]),
179 DataType::F32 => Self::F32(vec![value as f32; len]),
180 DataType::F64 => Self::F64(vec![value; len]),
181 }
182 }
183
184 pub fn from_f64_vec(data_type: DataType, data: Vec<f64>) -> Self {
186 match data_type {
187 DataType::U8 => Self::U8(data.into_iter().map(|v| v as u8).collect()),
188 DataType::I8 => Self::I8(data.into_iter().map(|v| v as i8).collect()),
189 DataType::U16 => Self::U16(data.into_iter().map(|v| v as u16).collect()),
190 DataType::I16 => Self::I16(data.into_iter().map(|v| v as i16).collect()),
191 DataType::U32 => Self::U32(data.into_iter().map(|v| v as u32).collect()),
192 DataType::I32 => Self::I32(data.into_iter().map(|v| v as i32).collect()),
193 DataType::U64 => Self::U64(data.into_iter().map(|v| v as u64).collect()),
194 DataType::I64 => Self::I64(data.into_iter().map(|v| v as i64).collect()),
195 DataType::F32 => Self::F32(data.into_iter().map(|v| v as f32).collect()),
196 DataType::F64 => Self::F64(data),
197 }
198 }
199
200 pub fn len(&self) -> usize {
202 match self {
203 Self::U8(v) => v.len(),
204 Self::I8(v) => v.len(),
205 Self::U16(v) => v.len(),
206 Self::I16(v) => v.len(),
207 Self::U32(v) => v.len(),
208 Self::I32(v) => v.len(),
209 Self::U64(v) => v.len(),
210 Self::I64(v) => v.len(),
211 Self::F32(v) => v.len(),
212 Self::F64(v) => v.len(),
213 }
214 }
215
216 pub fn is_empty(&self) -> bool {
218 self.len() == 0
219 }
220
221 pub fn data_type(&self) -> DataType {
223 match self {
224 Self::U8(_) => DataType::U8,
225 Self::I8(_) => DataType::I8,
226 Self::U16(_) => DataType::U16,
227 Self::I16(_) => DataType::I16,
228 Self::U32(_) => DataType::U32,
229 Self::I32(_) => DataType::I32,
230 Self::U64(_) => DataType::U64,
231 Self::I64(_) => DataType::I64,
232 Self::F32(_) => DataType::F32,
233 Self::F64(_) => DataType::F64,
234 }
235 }
236
237 pub fn get_f64(&self, idx: usize) -> f64 {
239 match self {
240 Self::U8(v) => v[idx] as f64,
241 Self::I8(v) => v[idx] as f64,
242 Self::U16(v) => v[idx] as f64,
243 Self::I16(v) => v[idx] as f64,
244 Self::U32(v) => v[idx] as f64,
245 Self::I32(v) => v[idx] as f64,
246 Self::U64(v) => v[idx] as f64,
247 Self::I64(v) => v[idx] as f64,
248 Self::F32(v) => v[idx] as f64,
249 Self::F64(v) => v[idx],
250 }
251 }
252
253 pub fn set_f64(&mut self, idx: usize, value: f64) {
255 match self {
256 Self::U8(v) => v[idx] = value as u8,
257 Self::I8(v) => v[idx] = value as i8,
258 Self::U16(v) => v[idx] = value as u16,
259 Self::I16(v) => v[idx] = value as i16,
260 Self::U32(v) => v[idx] = value as u32,
261 Self::I32(v) => v[idx] = value as i32,
262 Self::U64(v) => v[idx] = value as u64,
263 Self::I64(v) => v[idx] = value as i64,
264 Self::F32(v) => v[idx] = value as f32,
265 Self::F64(v) => v[idx] = value,
266 }
267 }
268
269 pub fn iter_f64(&self) -> Box<dyn Iterator<Item = f64> + '_> {
271 match self {
272 Self::U8(v) => Box::new(v.iter().copied().map(|x| x as f64)),
273 Self::I8(v) => Box::new(v.iter().copied().map(|x| x as f64)),
274 Self::U16(v) => Box::new(v.iter().copied().map(|x| x as f64)),
275 Self::I16(v) => Box::new(v.iter().copied().map(|x| x as f64)),
276 Self::U32(v) => Box::new(v.iter().copied().map(|x| x as f64)),
277 Self::I32(v) => Box::new(v.iter().copied().map(|x| x as f64)),
278 Self::U64(v) => Box::new(v.iter().copied().map(|x| x as f64)),
279 Self::I64(v) => Box::new(v.iter().copied().map(|x| x as f64)),
280 Self::F32(v) => Box::new(v.iter().copied().map(|x| x as f64)),
281 Self::F64(v) => Box::new(v.iter().copied()),
282 }
283 }
284
285 pub fn to_f64_vec(&self) -> Vec<f64> {
287 self.iter_f64().collect()
288 }
289
290 pub fn as_u8_slice(&self) -> Option<&[u8]> {
292 match self {
293 Self::U8(v) => Some(v.as_slice()),
294 _ => None,
295 }
296 }
297
298 pub fn as_u8_slice_mut(&mut self) -> Option<&mut [u8]> {
300 match self {
301 Self::U8(v) => Some(v.as_mut_slice()),
302 _ => None,
303 }
304 }
305
306 pub fn as_i8_slice(&self) -> Option<&[i8]> {
308 match self {
309 Self::I8(v) => Some(v.as_slice()),
310 _ => None,
311 }
312 }
313
314 pub fn as_i8_slice_mut(&mut self) -> Option<&mut [i8]> {
316 match self {
317 Self::I8(v) => Some(v.as_mut_slice()),
318 _ => None,
319 }
320 }
321
322 pub fn as_u16_slice(&self) -> Option<&[u16]> {
324 match self {
325 Self::U16(v) => Some(v.as_slice()),
326 _ => None,
327 }
328 }
329
330 pub fn as_u16_slice_mut(&mut self) -> Option<&mut [u16]> {
332 match self {
333 Self::U16(v) => Some(v.as_mut_slice()),
334 _ => None,
335 }
336 }
337
338 pub fn as_i16_slice(&self) -> Option<&[i16]> {
340 match self {
341 Self::I16(v) => Some(v.as_slice()),
342 _ => None,
343 }
344 }
345
346 pub fn as_i16_slice_mut(&mut self) -> Option<&mut [i16]> {
348 match self {
349 Self::I16(v) => Some(v.as_mut_slice()),
350 _ => None,
351 }
352 }
353
354 pub fn as_u32_slice(&self) -> Option<&[u32]> {
356 match self {
357 Self::U32(v) => Some(v.as_slice()),
358 _ => None,
359 }
360 }
361
362 pub fn as_u32_slice_mut(&mut self) -> Option<&mut [u32]> {
364 match self {
365 Self::U32(v) => Some(v.as_mut_slice()),
366 _ => None,
367 }
368 }
369
370 pub fn as_i32_slice(&self) -> Option<&[i32]> {
372 match self {
373 Self::I32(v) => Some(v.as_slice()),
374 _ => None,
375 }
376 }
377
378 pub fn as_i32_slice_mut(&mut self) -> Option<&mut [i32]> {
380 match self {
381 Self::I32(v) => Some(v.as_mut_slice()),
382 _ => None,
383 }
384 }
385
386 pub fn as_u64_slice(&self) -> Option<&[u64]> {
388 match self {
389 Self::U64(v) => Some(v.as_slice()),
390 _ => None,
391 }
392 }
393
394 pub fn as_u64_slice_mut(&mut self) -> Option<&mut [u64]> {
396 match self {
397 Self::U64(v) => Some(v.as_mut_slice()),
398 _ => None,
399 }
400 }
401
402 pub fn as_i64_slice(&self) -> Option<&[i64]> {
404 match self {
405 Self::I64(v) => Some(v.as_slice()),
406 _ => None,
407 }
408 }
409
410 pub fn as_i64_slice_mut(&mut self) -> Option<&mut [i64]> {
412 match self {
413 Self::I64(v) => Some(v.as_mut_slice()),
414 _ => None,
415 }
416 }
417
418 pub fn as_f32_slice(&self) -> Option<&[f32]> {
420 match self {
421 Self::F32(v) => Some(v.as_slice()),
422 _ => None,
423 }
424 }
425
426 pub fn as_f32_slice_mut(&mut self) -> Option<&mut [f32]> {
428 match self {
429 Self::F32(v) => Some(v.as_mut_slice()),
430 _ => None,
431 }
432 }
433
434 pub fn as_f64_slice(&self) -> Option<&[f64]> {
436 match self {
437 Self::F64(v) => Some(v.as_slice()),
438 _ => None,
439 }
440 }
441
442 pub fn as_f64_slice_mut(&mut self) -> Option<&mut [f64]> {
444 match self {
445 Self::F64(v) => Some(v.as_mut_slice()),
446 _ => None,
447 }
448 }
449}
450
451#[derive(Debug, Clone, Copy, PartialEq)]
455pub struct NoData(pub f64);
456
457impl NoData {
458 pub const COMMON: NoData = NoData(-9999.0);
460 pub const NAN: NoData = NoData(f64::NAN);
462
463 #[inline]
465 pub fn matches(self, v: f64) -> bool {
466 if self.0.is_nan() {
467 v.is_nan()
468 } else {
469 (v - self.0).abs() < f64::EPSILON * self.0.abs().max(1.0)
470 }
471 }
472}
473
474impl Default for NoData {
475 fn default() -> Self {
476 Self::COMMON
477 }
478}
479
480#[derive(Debug, Clone, Copy, PartialEq)]
484pub struct Extent {
485 pub x_min: f64,
487 pub y_min: f64,
489 pub x_max: f64,
491 pub y_max: f64,
493}
494
495impl Extent {
496 pub fn from_origin(x_min: f64, y_min: f64, cols: usize, rows: usize, cell_size: f64) -> Self {
498 Self {
499 x_min,
500 y_min,
501 x_max: x_min + cols as f64 * cell_size,
502 y_max: y_min + rows as f64 * cell_size,
503 }
504 }
505
506 pub fn width(&self) -> f64 { self.x_max - self.x_min }
508
509 pub fn height(&self) -> f64 { self.y_max - self.y_min }
511}
512
513#[derive(Debug, Clone, Copy, PartialEq)]
517pub struct Statistics {
518 pub min: f64,
520 pub max: f64,
522 pub mean: f64,
524 pub std_dev: f64,
526 pub valid_count: usize,
528 pub nodata_count: usize,
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq)]
534pub enum StatisticsComputationMode {
535 Auto,
537 Scalar,
539 Simd,
541}
542
543#[derive(Debug, Clone, Copy)]
544struct StatsAccumulator {
545 min: f64,
546 max: f64,
547 sum: f64,
548 sum_sq: f64,
549 valid_count: usize,
550 nodata_count: usize,
551}
552
553impl Default for StatsAccumulator {
554 fn default() -> Self {
555 Self {
556 min: f64::INFINITY,
557 max: f64::NEG_INFINITY,
558 sum: 0.0,
559 sum_sq: 0.0,
560 valid_count: 0,
561 nodata_count: 0,
562 }
563 }
564}
565
566impl StatsAccumulator {
567 fn merge(&mut self, other: Self) {
568 self.min = self.min.min(other.min);
569 self.max = self.max.max(other.max);
570 self.sum += other.sum;
571 self.sum_sq += other.sum_sq;
572 self.valid_count += other.valid_count;
573 self.nodata_count += other.nodata_count;
574 }
575
576 fn to_statistics(self) -> Statistics {
577 let (mean, std_dev) = if self.valid_count == 0 {
578 (0.0, 0.0)
579 } else {
580 let n = self.valid_count as f64;
581 let mean = self.sum / n;
582 let variance = (self.sum_sq / n - mean * mean).max(0.0);
583 (mean, variance.sqrt())
584 };
585
586 Statistics {
587 min: if self.valid_count == 0 { 0.0 } else { self.min },
588 max: if self.valid_count == 0 { 0.0 } else { self.max },
589 mean,
590 std_dev,
591 valid_count: self.valid_count,
592 nodata_count: self.nodata_count,
593 }
594 }
595}
596
597#[derive(Debug, Clone, Copy, PartialEq, Eq)]
599pub enum ResampleMethod {
600 Nearest,
602 Bilinear,
604 Cubic,
606 Lanczos,
608 Average,
610 Min,
612 Max,
614 Mode,
616 Median,
618 StdDev,
620}
621
622#[derive(Debug, Clone, Copy, PartialEq, Eq)]
624pub enum NodataPolicy {
625 Strict,
627 PartialKernel,
629 Fill,
631}
632
633#[derive(Debug, Clone, Copy, PartialEq, Eq)]
635pub enum AntimeridianPolicy {
636 Auto,
638 Linear,
640 Wrap,
642}
643
644#[derive(Debug, Clone, Copy, PartialEq, Eq)]
646pub enum GridSizePolicy {
647 Expand,
649 FitInside,
651}
652
653#[derive(Debug, Clone, Copy, PartialEq, Eq)]
655pub enum DestinationFootprint {
656 None,
658 SourceBoundary,
660}
661
662#[derive(Debug, Clone, Copy)]
664pub struct ReprojectOptions {
665 pub dst_epsg: u32,
667 pub resample: ResampleMethod,
669 pub cols: Option<usize>,
671 pub rows: Option<usize>,
673 pub extent: Option<Extent>,
677 pub x_res: Option<f64>,
681 pub y_res: Option<f64>,
685 pub snap_x: Option<f64>,
689 pub snap_y: Option<f64>,
693 pub nodata_policy: NodataPolicy,
695 pub antimeridian_policy: AntimeridianPolicy,
697 pub grid_size_policy: GridSizePolicy,
699 pub destination_footprint: DestinationFootprint,
701}
702
703impl ReprojectOptions {
704 pub fn new(dst_epsg: u32, resample: ResampleMethod) -> Self {
706 Self {
707 dst_epsg,
708 resample,
709 cols: None,
710 rows: None,
711 extent: None,
712 x_res: None,
713 y_res: None,
714 snap_x: None,
715 snap_y: None,
716 nodata_policy: NodataPolicy::PartialKernel,
717 antimeridian_policy: AntimeridianPolicy::Auto,
718 grid_size_policy: GridSizePolicy::Expand,
719 destination_footprint: DestinationFootprint::None,
720 }
721 }
722
723 pub fn with_nodata_policy(mut self, nodata_policy: NodataPolicy) -> Self {
725 self.nodata_policy = nodata_policy;
726 self
727 }
728
729 pub fn with_size(mut self, cols: usize, rows: usize) -> Self {
731 self.cols = Some(cols);
732 self.rows = Some(rows);
733 self
734 }
735
736 pub fn with_extent(mut self, extent: Extent) -> Self {
738 self.extent = Some(extent);
739 self
740 }
741
742 pub fn with_resolution(mut self, x_res: f64, y_res: f64) -> Self {
747 self.x_res = Some(x_res);
748 self.y_res = Some(y_res);
749 self
750 }
751
752 pub fn with_square_resolution(mut self, res: f64) -> Self {
754 self.x_res = Some(res);
755 self.y_res = Some(res);
756 self
757 }
758
759 pub fn with_snap_origin(mut self, snap_x: f64, snap_y: f64) -> Self {
764 self.snap_x = Some(snap_x);
765 self.snap_y = Some(snap_y);
766 self
767 }
768
769 pub fn with_antimeridian_policy(mut self, policy: AntimeridianPolicy) -> Self {
771 self.antimeridian_policy = policy;
772 self
773 }
774
775 pub fn with_grid_size_policy(mut self, policy: GridSizePolicy) -> Self {
777 self.grid_size_policy = policy;
778 self
779 }
780
781 pub fn with_destination_footprint(mut self, footprint: DestinationFootprint) -> Self {
783 self.destination_footprint = footprint;
784 self
785 }
786}
787
788#[derive(Debug, Clone)]
792pub struct RasterConfig {
793 pub cols: usize,
795 pub rows: usize,
797 pub bands: usize,
799 pub x_min: f64,
801 pub y_min: f64,
803 pub cell_size: f64,
805 pub cell_size_y: Option<f64>,
808 pub nodata: f64,
810 pub data_type: DataType,
812 pub crs: CrsInfo,
814 pub metadata: Vec<(String, String)>,
816}
817
818impl Default for RasterConfig {
819 fn default() -> Self {
820 Self {
821 cols: 0,
822 rows: 0,
823 bands: 1,
824 x_min: 0.0,
825 y_min: 0.0,
826 cell_size: 1.0,
827 cell_size_y: None,
828 nodata: -9999.0,
829 data_type: DataType::F32,
830 crs: CrsInfo::default(),
831 metadata: Vec::new(),
832 }
833 }
834}
835
836#[derive(Debug, Clone)]
846pub struct Raster {
847 pub cols: usize,
849 pub rows: usize,
851 pub bands: usize,
853 pub x_min: f64,
855 pub y_min: f64,
857 pub cell_size_x: f64,
859 pub cell_size_y: f64,
861 pub nodata: f64,
863 pub data_type: DataType,
865 pub crs: CrsInfo,
867 pub metadata: Vec<(String, String)>,
869 pub data: RasterData,
871}
872
873impl Raster {
874 pub fn new(cfg: RasterConfig) -> Self {
878 let bands = cfg.bands.max(1);
879 let n = cfg.cols * cfg.rows * bands;
880 let cell_size_y = cfg.cell_size_y.map(|v| v.abs()).unwrap_or(cfg.cell_size);
881 Self {
882 cols: cfg.cols,
883 rows: cfg.rows,
884 bands,
885 x_min: cfg.x_min,
886 y_min: cfg.y_min,
887 cell_size_x: cfg.cell_size,
888 cell_size_y,
889 nodata: cfg.nodata,
890 data_type: cfg.data_type,
891 crs: cfg.crs,
892 metadata: cfg.metadata,
893 data: RasterData::new_filled(cfg.data_type, n, cfg.nodata),
894 }
895 }
896
897 pub fn from_data(cfg: RasterConfig, data: Vec<f64>) -> Result<Self> {
902 let bands = cfg.bands.max(1);
903 if data.len() != cfg.cols * cfg.rows * bands {
904 return Err(RasterError::InvalidDimensions { cols: cfg.cols, rows: cfg.rows });
905 }
906 let dt = cfg.data_type;
907 let mut r = Self::new(cfg);
908 r.data = RasterData::from_f64_vec(dt, data);
909 Ok(r)
910 }
911
912 pub fn from_data_native(cfg: RasterConfig, data: RasterData) -> Result<Self> {
918 let bands = cfg.bands.max(1);
919 if data.len() != cfg.cols * cfg.rows * bands {
920 return Err(RasterError::InvalidDimensions { cols: cfg.cols, rows: cfg.rows });
921 }
922 if cfg.data_type != data.data_type() {
923 return Err(RasterError::Other(format!(
924 "data_type mismatch: config={}, data={}",
925 cfg.data_type,
926 data.data_type()
927 )));
928 }
929 let mut r = Self::new(cfg);
930 r.data = data;
931 Ok(r)
932 }
933
934 pub fn data_u8(&self) -> Option<&[u8]> { self.data.as_u8_slice() }
936 pub fn data_u8_mut(&mut self) -> Option<&mut [u8]> { self.data.as_u8_slice_mut() }
938
939 pub fn data_i8(&self) -> Option<&[i8]> { self.data.as_i8_slice() }
941 pub fn data_i8_mut(&mut self) -> Option<&mut [i8]> { self.data.as_i8_slice_mut() }
943
944 pub fn data_u16(&self) -> Option<&[u16]> { self.data.as_u16_slice() }
946 pub fn data_u16_mut(&mut self) -> Option<&mut [u16]> { self.data.as_u16_slice_mut() }
948
949 pub fn data_i16(&self) -> Option<&[i16]> { self.data.as_i16_slice() }
951 pub fn data_i16_mut(&mut self) -> Option<&mut [i16]> { self.data.as_i16_slice_mut() }
953
954 pub fn data_u32(&self) -> Option<&[u32]> { self.data.as_u32_slice() }
956 pub fn data_u32_mut(&mut self) -> Option<&mut [u32]> { self.data.as_u32_slice_mut() }
958
959 pub fn data_i32(&self) -> Option<&[i32]> { self.data.as_i32_slice() }
961 pub fn data_i32_mut(&mut self) -> Option<&mut [i32]> { self.data.as_i32_slice_mut() }
963
964 pub fn data_u64(&self) -> Option<&[u64]> { self.data.as_u64_slice() }
966 pub fn data_u64_mut(&mut self) -> Option<&mut [u64]> { self.data.as_u64_slice_mut() }
968
969 pub fn data_i64(&self) -> Option<&[i64]> { self.data.as_i64_slice() }
971 pub fn data_i64_mut(&mut self) -> Option<&mut [i64]> { self.data.as_i64_slice_mut() }
973
974 pub fn data_f32(&self) -> Option<&[f32]> { self.data.as_f32_slice() }
976 pub fn data_f32_mut(&mut self) -> Option<&mut [f32]> { self.data.as_f32_slice_mut() }
978
979 pub fn data_f64(&self) -> Option<&[f64]> { self.data.as_f64_slice() }
981 pub fn data_f64_mut(&mut self) -> Option<&mut [f64]> { self.data.as_f64_slice_mut() }
983
984 #[inline]
989 pub fn index(&self, band: isize, row: isize, col: isize) -> Option<usize> {
990 if band < 0
991 || row < 0
992 || col < 0
993 || band >= self.bands as isize
994 || row >= self.rows as isize
995 || col >= self.cols as isize
996 {
997 return None;
998 }
999 let band = band as usize;
1000 let row = row as usize;
1001 let col = col as usize;
1002 let band_stride = self.rows * self.cols;
1003 Some(band * band_stride + row * self.cols + col)
1004 }
1005
1006 #[inline]
1011 pub fn get(&self, band: isize, row: isize, col: isize) -> f64 {
1012 self.get_raw(band, row, col).unwrap_or(self.nodata)
1013 }
1014
1015 #[inline]
1020 pub fn get_opt(&self, band: isize, row: isize, col: isize) -> Option<f64> {
1021 let idx = self.index(band, row, col)?;
1022 let v = self.data.get_f64(idx);
1023 if self.is_nodata(v) { None } else { Some(v) }
1024 }
1025
1026 #[inline]
1029 pub fn get_raw(&self, band: isize, row: isize, col: isize) -> Option<f64> {
1030 let idx = self.index(band, row, col)?;
1031 Some(self.data.get_f64(idx))
1032 }
1033
1034 #[inline]
1039 pub fn set(&mut self, band: isize, row: isize, col: isize, value: f64) -> Result<()> {
1040 if band < 0
1041 || row < 0
1042 || col < 0
1043 || band >= self.bands as isize
1044 || row >= self.rows as isize
1045 || col >= self.cols as isize
1046 {
1047 return Err(RasterError::OutOfBounds {
1048 band,
1049 col,
1050 row,
1051 bands: self.bands,
1052 cols: self.cols,
1053 rows: self.rows,
1054 });
1055 }
1056 let idx = self.index(band, row, col).expect("set bounds prechecked");
1057 self.data.set_f64(idx, value);
1058 Ok(())
1059 }
1060
1061 #[inline]
1063 pub fn set_unchecked(&mut self, band: isize, row: isize, col: isize, value: f64) {
1064 let idx = self
1065 .index(band, row, col)
1066 .expect("set_unchecked requires in-bounds coordinates");
1067 self.data.set_f64(idx, value);
1068 }
1069
1070 #[inline]
1072 pub fn is_nodata(&self, v: f64) -> bool {
1073 if self.nodata.is_nan() {
1074 v.is_nan()
1075 } else {
1076 (v - self.nodata).abs() < 1e-10 * self.nodata.abs().max(1.0)
1077 }
1078 }
1079
1080 #[inline]
1084 pub fn y_max(&self) -> f64 {
1085 self.y_min + self.rows as f64 * self.cell_size_y
1086 }
1087
1088 #[inline]
1090 pub fn x_max(&self) -> f64 {
1091 self.x_min + self.cols as f64 * self.cell_size_x
1092 }
1093
1094 pub fn extent(&self) -> Extent {
1096 Extent {
1097 x_min: self.x_min,
1098 y_min: self.y_min,
1099 x_max: self.x_max(),
1100 y_max: self.y_max(),
1101 }
1102 }
1103
1104 #[inline]
1106 pub fn col_center_x(&self, col: isize) -> f64 {
1107 self.x_min + (col as f64 + 0.5) * self.cell_size_x
1108 }
1109
1110 #[inline]
1112 pub fn row_center_y(&self, row: isize) -> f64 {
1113 self.y_max() - (row as f64 + 0.5) * self.cell_size_y
1114 }
1115
1116 pub fn world_to_pixel(&self, x: f64, y: f64) -> Option<(isize, isize)> {
1119 if x < self.x_min || x >= self.x_max() || y < self.y_min || y >= self.y_max() {
1120 return None;
1121 }
1122 let col = ((x - self.x_min) / self.cell_size_x).floor() as isize;
1123 let row = ((self.y_max() - y) / self.cell_size_y).floor() as isize;
1124 let col = col.min(self.cols as isize - 1);
1125 let row = row.min(self.rows as isize - 1);
1126 Some((col, row))
1127 }
1128
1129 pub fn assign_crs_epsg(&mut self, epsg: u32) {
1134 self.crs = CrsInfo {
1135 epsg: Some(epsg),
1136 wkt: None,
1137 proj4: None,
1138 };
1139 }
1140
1141 pub fn assign_crs_wkt(&mut self, wkt: &str) {
1146 self.crs = CrsInfo {
1147 epsg: None,
1148 wkt: Some(wkt.to_string()),
1149 proj4: None,
1150 };
1151 }
1152
1153 pub fn reproject_to_epsg(&self, dst_epsg: u32, resample: ResampleMethod) -> Result<Raster> {
1166 self.reproject_with_options(&ReprojectOptions::new(dst_epsg, resample))
1167 }
1168
1169 pub fn reproject_with_options(&self, options: &ReprojectOptions) -> Result<Raster> {
1171 let src_epsg = self.crs.epsg.ok_or_else(|| {
1172 RasterError::Other(
1173 "reproject_to_epsg requires source CRS EPSG in raster.crs.epsg".to_string(),
1174 )
1175 })?;
1176 let src_crs = Crs::from_epsg(src_epsg).map_err(|e| {
1177 RasterError::Other(format!("source EPSG {src_epsg} is not supported: {e}"))
1178 })?;
1179 let dst_crs = Crs::from_epsg(options.dst_epsg).map_err(|e| {
1180 RasterError::Other(format!(
1181 "destination EPSG {} is not supported: {e}",
1182 options.dst_epsg
1183 ))
1184 })?;
1185
1186 self.reproject_internal(&src_crs, &dst_crs, options, None)
1187 }
1188
1189 pub fn reproject_with_options_and_progress<F>(
1192 &self,
1193 options: &ReprojectOptions,
1194 progress: F,
1195 ) -> Result<Raster>
1196 where
1197 F: Fn(f64) + Send + Sync,
1198 {
1199 let src_epsg = self.crs.epsg.ok_or_else(|| {
1200 RasterError::Other(
1201 "reproject_to_epsg requires source CRS EPSG in raster.crs.epsg".to_string(),
1202 )
1203 })?;
1204 let src_crs = Crs::from_epsg(src_epsg).map_err(|e| {
1205 RasterError::Other(format!("source EPSG {src_epsg} is not supported: {e}"))
1206 })?;
1207 let dst_crs = Crs::from_epsg(options.dst_epsg).map_err(|e| {
1208 RasterError::Other(format!(
1209 "destination EPSG {} is not supported: {e}",
1210 options.dst_epsg
1211 ))
1212 })?;
1213
1214 self.reproject_internal(&src_crs, &dst_crs, options, Some(&progress))
1215 }
1216
1217 pub fn reproject_with_crs(
1225 &self,
1226 src_crs: &Crs,
1227 dst_crs: &Crs,
1228 options: &ReprojectOptions,
1229 ) -> Result<Raster> {
1230 self.reproject_internal(src_crs, dst_crs, options, None)
1231 }
1232
1233 pub fn reproject_with_crs_and_progress<F>(
1236 &self,
1237 src_crs: &Crs,
1238 dst_crs: &Crs,
1239 options: &ReprojectOptions,
1240 progress: F,
1241 ) -> Result<Raster>
1242 where
1243 F: Fn(f64) + Send + Sync,
1244 {
1245 self.reproject_internal(src_crs, dst_crs, options, Some(&progress))
1246 }
1247
1248 pub fn reproject_to_epsg_nearest(&self, dst_epsg: u32) -> Result<Raster> {
1250 self.reproject_to_epsg(dst_epsg, ResampleMethod::Nearest)
1251 }
1252
1253 pub fn reproject_to_epsg_bilinear(&self, dst_epsg: u32) -> Result<Raster> {
1255 self.reproject_to_epsg(dst_epsg, ResampleMethod::Bilinear)
1256 }
1257
1258 pub fn reproject_to_epsg_cubic(&self, dst_epsg: u32) -> Result<Raster> {
1260 self.reproject_to_epsg(dst_epsg, ResampleMethod::Cubic)
1261 }
1262
1263 pub fn reproject_to_epsg_lanczos(&self, dst_epsg: u32) -> Result<Raster> {
1265 self.reproject_to_epsg(dst_epsg, ResampleMethod::Lanczos)
1266 }
1267
1268 pub fn reproject_to_epsg_average(&self, dst_epsg: u32) -> Result<Raster> {
1270 self.reproject_to_epsg(dst_epsg, ResampleMethod::Average)
1271 }
1272
1273 pub fn reproject_to_epsg_min(&self, dst_epsg: u32) -> Result<Raster> {
1275 self.reproject_to_epsg(dst_epsg, ResampleMethod::Min)
1276 }
1277
1278 pub fn reproject_to_epsg_max(&self, dst_epsg: u32) -> Result<Raster> {
1280 self.reproject_to_epsg(dst_epsg, ResampleMethod::Max)
1281 }
1282
1283 pub fn reproject_to_epsg_mode(&self, dst_epsg: u32) -> Result<Raster> {
1285 self.reproject_to_epsg(dst_epsg, ResampleMethod::Mode)
1286 }
1287
1288 pub fn reproject_to_epsg_median(&self, dst_epsg: u32) -> Result<Raster> {
1290 self.reproject_to_epsg(dst_epsg, ResampleMethod::Median)
1291 }
1292
1293 pub fn reproject_to_epsg_stddev(&self, dst_epsg: u32) -> Result<Raster> {
1295 self.reproject_to_epsg(dst_epsg, ResampleMethod::StdDev)
1296 }
1297
1298 pub fn reproject_to_match_grid(
1307 &self,
1308 target_grid: &Raster,
1309 resample: ResampleMethod,
1310 ) -> Result<Raster> {
1311 let dst_epsg = target_grid.crs.epsg.ok_or_else(|| {
1312 RasterError::Other(
1313 "reproject_to_match_grid requires target grid CRS EPSG in target_grid.crs.epsg"
1314 .to_string(),
1315 )
1316 })?;
1317
1318 let opts = ReprojectOptions::new(dst_epsg, resample)
1319 .with_size(target_grid.cols, target_grid.rows)
1320 .with_extent(target_grid.extent());
1321
1322 self.reproject_with_options(&opts)
1323 }
1324
1325 pub fn reproject_to_match_grid_and_progress<F>(
1328 &self,
1329 target_grid: &Raster,
1330 resample: ResampleMethod,
1331 progress: F,
1332 ) -> Result<Raster>
1333 where
1334 F: Fn(f64) + Send + Sync,
1335 {
1336 let dst_epsg = target_grid.crs.epsg.ok_or_else(|| {
1337 RasterError::Other(
1338 "reproject_to_match_grid requires target grid CRS EPSG in target_grid.crs.epsg"
1339 .to_string(),
1340 )
1341 })?;
1342
1343 let opts = ReprojectOptions::new(dst_epsg, resample)
1344 .with_size(target_grid.cols, target_grid.rows)
1345 .with_extent(target_grid.extent());
1346
1347 self.reproject_with_options_and_progress(&opts, progress)
1348 }
1349
1350 pub fn reproject_to_match_resolution(
1359 &self,
1360 reference_grid: &Raster,
1361 resample: ResampleMethod,
1362 ) -> Result<Raster> {
1363 let dst_epsg = reference_grid.crs.epsg.ok_or_else(|| {
1364 RasterError::Other(
1365 "reproject_to_match_resolution requires reference grid CRS EPSG in reference_grid.crs.epsg"
1366 .to_string(),
1367 )
1368 })?;
1369
1370 let opts = ReprojectOptions::new(dst_epsg, resample)
1371 .with_resolution(reference_grid.cell_size_x, reference_grid.cell_size_y)
1372 .with_snap_origin(reference_grid.x_min, reference_grid.y_min);
1373
1374 self.reproject_with_options(&opts)
1375 }
1376
1377 pub fn reproject_to_match_resolution_and_progress<F>(
1380 &self,
1381 reference_grid: &Raster,
1382 resample: ResampleMethod,
1383 progress: F,
1384 ) -> Result<Raster>
1385 where
1386 F: Fn(f64) + Send + Sync,
1387 {
1388 let dst_epsg = reference_grid.crs.epsg.ok_or_else(|| {
1389 RasterError::Other(
1390 "reproject_to_match_resolution requires reference grid CRS EPSG in reference_grid.crs.epsg"
1391 .to_string(),
1392 )
1393 })?;
1394
1395 let opts = ReprojectOptions::new(dst_epsg, resample)
1396 .with_resolution(reference_grid.cell_size_x, reference_grid.cell_size_y)
1397 .with_snap_origin(reference_grid.x_min, reference_grid.y_min);
1398
1399 self.reproject_with_options_and_progress(&opts, progress)
1400 }
1401
1402 pub fn reproject_to_match_resolution_in_epsg(
1413 &self,
1414 dst_epsg: u32,
1415 reference_grid: &Raster,
1416 resample: ResampleMethod,
1417 ) -> Result<Raster> {
1418 let reference_epsg = reference_grid.crs.epsg.ok_or_else(|| {
1419 RasterError::Other(
1420 "reproject_to_match_resolution_in_epsg requires reference grid CRS EPSG in reference_grid.crs.epsg"
1421 .to_string(),
1422 )
1423 })?;
1424
1425 let (snap_x, snap_y, x_res, y_res) = if reference_epsg == dst_epsg {
1426 (
1427 reference_grid.x_min,
1428 reference_grid.y_min,
1429 reference_grid.cell_size_x,
1430 reference_grid.cell_size_y,
1431 )
1432 } else {
1433 let ref_crs = Crs::from_epsg(reference_epsg).map_err(|e| {
1434 RasterError::Other(format!(
1435 "reference EPSG {reference_epsg} is not supported: {e}"
1436 ))
1437 })?;
1438 let dst_crs = Crs::from_epsg(dst_epsg).map_err(|e| {
1439 RasterError::Other(format!(
1440 "destination EPSG {dst_epsg} is not supported: {e}"
1441 ))
1442 })?;
1443
1444 let ox = reference_grid.x_min;
1445 let oy = reference_grid.y_min;
1446 let (sx, sy) = ref_crs.transform_to(ox, oy, &dst_crs).map_err(|e| {
1447 RasterError::Other(format!(
1448 "failed to transform reference snap origin to EPSG:{dst_epsg}: {e}"
1449 ))
1450 })?;
1451 let (sx_dx, sy_dx) = ref_crs
1452 .transform_to(ox + reference_grid.cell_size_x, oy, &dst_crs)
1453 .map_err(|e| {
1454 RasterError::Other(format!(
1455 "failed to transform reference X-step to EPSG:{dst_epsg}: {e}"
1456 ))
1457 })?;
1458 let (sx_dy, sy_dy) = ref_crs
1459 .transform_to(ox, oy + reference_grid.cell_size_y, &dst_crs)
1460 .map_err(|e| {
1461 RasterError::Other(format!(
1462 "failed to transform reference Y-step to EPSG:{dst_epsg}: {e}"
1463 ))
1464 })?;
1465
1466 let rx = (sx_dx - sx).hypot(sy_dx - sy);
1467 let ry = (sx_dy - sx).hypot(sy_dy - sy);
1468 if !rx.is_finite() || !ry.is_finite() || rx <= 0.0 || ry <= 0.0 {
1469 return Err(RasterError::Other(
1470 "invalid transformed reference resolution while matching destination EPSG"
1471 .to_string(),
1472 ));
1473 }
1474 (sx, sy, rx, ry)
1475 };
1476
1477 let opts = ReprojectOptions::new(dst_epsg, resample)
1478 .with_resolution(x_res, y_res)
1479 .with_snap_origin(snap_x, snap_y);
1480
1481 self.reproject_with_options(&opts)
1482 }
1483
1484 pub fn reproject_to_match_resolution_in_epsg_and_progress<F>(
1487 &self,
1488 dst_epsg: u32,
1489 reference_grid: &Raster,
1490 resample: ResampleMethod,
1491 progress: F,
1492 ) -> Result<Raster>
1493 where
1494 F: Fn(f64) + Send + Sync,
1495 {
1496 let reference_epsg = reference_grid.crs.epsg.ok_or_else(|| {
1497 RasterError::Other(
1498 "reproject_to_match_resolution_in_epsg requires reference grid CRS EPSG in reference_grid.crs.epsg"
1499 .to_string(),
1500 )
1501 })?;
1502
1503 let (snap_x, snap_y, x_res, y_res) = if reference_epsg == dst_epsg {
1504 (
1505 reference_grid.x_min,
1506 reference_grid.y_min,
1507 reference_grid.cell_size_x,
1508 reference_grid.cell_size_y,
1509 )
1510 } else {
1511 let ref_crs = Crs::from_epsg(reference_epsg).map_err(|e| {
1512 RasterError::Other(format!(
1513 "reference EPSG {reference_epsg} is not supported: {e}"
1514 ))
1515 })?;
1516 let dst_crs = Crs::from_epsg(dst_epsg).map_err(|e| {
1517 RasterError::Other(format!(
1518 "destination EPSG {dst_epsg} is not supported: {e}"
1519 ))
1520 })?;
1521
1522 let ox = reference_grid.x_min;
1523 let oy = reference_grid.y_min;
1524 let (sx, sy) = ref_crs.transform_to(ox, oy, &dst_crs).map_err(|e| {
1525 RasterError::Other(format!(
1526 "failed to transform reference snap origin to EPSG:{dst_epsg}: {e}"
1527 ))
1528 })?;
1529 let (sx_dx, sy_dx) = ref_crs
1530 .transform_to(ox + reference_grid.cell_size_x, oy, &dst_crs)
1531 .map_err(|e| {
1532 RasterError::Other(format!(
1533 "failed to transform reference X-step to EPSG:{dst_epsg}: {e}"
1534 ))
1535 })?;
1536 let (sx_dy, sy_dy) = ref_crs
1537 .transform_to(ox, oy + reference_grid.cell_size_y, &dst_crs)
1538 .map_err(|e| {
1539 RasterError::Other(format!(
1540 "failed to transform reference Y-step to EPSG:{dst_epsg}: {e}"
1541 ))
1542 })?;
1543
1544 let rx = (sx_dx - sx).hypot(sy_dx - sy);
1545 let ry = (sx_dy - sx).hypot(sy_dy - sy);
1546 if !rx.is_finite() || !ry.is_finite() || rx <= 0.0 || ry <= 0.0 {
1547 return Err(RasterError::Other(
1548 "invalid transformed reference resolution while matching destination EPSG"
1549 .to_string(),
1550 ));
1551 }
1552 (sx, sy, rx, ry)
1553 };
1554
1555 let opts = ReprojectOptions::new(dst_epsg, resample)
1556 .with_resolution(x_res, y_res)
1557 .with_snap_origin(snap_x, snap_y);
1558
1559 self.reproject_with_options_and_progress(&opts, progress)
1560 }
1561
1562 fn reproject_internal(
1563 &self,
1564 src_crs: &Crs,
1565 dst_crs: &Crs,
1566 options: &ReprojectOptions,
1567 progress: Option<&(dyn Fn(f64) + Send + Sync)>,
1568 ) -> Result<Raster> {
1569 let src_extent = self.extent();
1570 let samples_per_edge = (self.cols.max(self.rows) / 32).clamp(8, 128);
1571 let base_extent = transformed_extent_from_boundary_samples(
1572 src_crs,
1573 dst_crs,
1574 src_extent,
1575 samples_per_edge,
1576 options.dst_epsg,
1577 options.antimeridian_policy,
1578 )?;
1579 let out_extent = options.extent.unwrap_or(base_extent);
1580 let width = out_extent.x_max - out_extent.x_min;
1581 let height = out_extent.y_max - out_extent.y_min;
1582
1583 if !out_extent.x_min.is_finite()
1584 || !out_extent.x_max.is_finite()
1585 || !out_extent.y_min.is_finite()
1586 || !out_extent.y_max.is_finite()
1587 || width <= 0.0
1588 || height <= 0.0
1589 {
1590 return Err(RasterError::CorruptData(
1591 "invalid transformed extent produced during reprojection".to_string(),
1592 ));
1593 }
1594
1595 let x_res = options.x_res.map(f64::abs);
1596 let y_res = options.y_res.map(f64::abs);
1597 if x_res.is_some_and(|v| !v.is_finite() || v <= 0.0)
1598 || y_res.is_some_and(|v| !v.is_finite() || v <= 0.0)
1599 {
1600 return Err(RasterError::CorruptData(
1601 "invalid reprojection resolution (x_res/y_res must be positive finite values)"
1602 .to_string(),
1603 ));
1604 }
1605
1606 let mut x_min = out_extent.x_min;
1607 let mut x_max = out_extent.x_max;
1608 let mut y_min = out_extent.y_min;
1609 let mut y_max = out_extent.y_max;
1610
1611 let out_cols = match options.cols {
1612 Some(cols) => cols,
1613 None => match x_res {
1614 Some(rx) => {
1615 if let Some(sx) = options.snap_x {
1616 match options.grid_size_policy {
1617 GridSizePolicy::Expand => {
1618 x_min = snap_down_to_origin(x_min, sx, rx);
1619 x_max = snap_up_to_origin(x_max, sx, rx);
1620 }
1621 GridSizePolicy::FitInside => {
1622 x_min = snap_up_to_origin(x_min, sx, rx);
1623 x_max = snap_down_to_origin(x_max, sx, rx);
1624 }
1625 }
1626 }
1627 let span = (x_max - x_min).max(0.0);
1628 let cols = match options.grid_size_policy {
1629 GridSizePolicy::Expand => (span / rx).ceil().max(1.0) as usize,
1630 GridSizePolicy::FitInside => (span / rx).floor().max(1.0) as usize,
1631 };
1632 x_max = x_min + cols as f64 * rx;
1633 cols
1634 }
1635 None => self.cols,
1636 },
1637 };
1638 let out_rows = match options.rows {
1639 Some(rows) => rows,
1640 None => match y_res {
1641 Some(ry) => {
1642 if let Some(sy) = options.snap_y {
1643 match options.grid_size_policy {
1644 GridSizePolicy::Expand => {
1645 y_min = snap_down_to_origin(y_min, sy, ry);
1646 y_max = snap_up_to_origin(y_max, sy, ry);
1647 }
1648 GridSizePolicy::FitInside => {
1649 y_min = snap_up_to_origin(y_min, sy, ry);
1650 y_max = snap_down_to_origin(y_max, sy, ry);
1651 }
1652 }
1653 }
1654 let span = (y_max - y_min).max(0.0);
1655 let rows = match options.grid_size_policy {
1656 GridSizePolicy::Expand => (span / ry).ceil().max(1.0) as usize,
1657 GridSizePolicy::FitInside => (span / ry).floor().max(1.0) as usize,
1658 };
1659 y_max = y_min + rows as f64 * ry;
1660 rows
1661 }
1662 None => self.rows,
1663 },
1664 };
1665
1666 let out_extent = Extent {
1667 x_min,
1668 y_min,
1669 x_max,
1670 y_max,
1671 };
1672
1673 if out_cols == 0 || out_rows == 0 {
1674 return Err(RasterError::InvalidDimensions {
1675 cols: out_cols,
1676 rows: out_rows,
1677 });
1678 }
1679
1680 let cfg = RasterConfig {
1681 cols: out_cols,
1682 rows: out_rows,
1683 bands: self.bands,
1684 x_min: out_extent.x_min,
1685 y_min: out_extent.y_min,
1686 cell_size: (out_extent.x_max - out_extent.x_min) / out_cols as f64,
1687 cell_size_y: Some((out_extent.y_max - out_extent.y_min) / out_rows as f64),
1688 nodata: self.nodata,
1689 data_type: self.data_type,
1690 crs: CrsInfo::from_epsg(options.dst_epsg),
1691 metadata: self.metadata.clone(),
1692 };
1693 let mut out = Raster::new(cfg);
1694
1695 let out_y_max = out.y_max();
1696 let footprint_ring = if options.destination_footprint == DestinationFootprint::SourceBoundary {
1697 let ring = transformed_boundary_ring_samples(
1698 src_crs,
1699 dst_crs,
1700 src_extent,
1701 samples_per_edge,
1702 options.dst_epsg,
1703 options.antimeridian_policy,
1704 )?;
1705 if ring.len() >= 3 {
1706 Some(ring)
1707 } else {
1708 None
1709 }
1710 } else {
1711 None
1712 };
1713
1714 let total_rows = out.rows;
1715 let completed_rows = AtomicUsize::new(0);
1716 let rows_data: Vec<Vec<Option<f64>>> = (0..out.rows as isize)
1717 .into_par_iter()
1718 .map(|row| {
1719 let mut row_values = vec![None; out.cols * out.bands];
1720 let y = out_y_max - (row as f64 + 0.5) * out.cell_size_y;
1721
1722 let mut batch_coords: Vec<(f64, f64)> = Vec::with_capacity(out.cols);
1726 let mut batch_cols: Vec<usize> = Vec::with_capacity(out.cols);
1727 for col in 0..out.cols as isize {
1728 let x = out.x_min + (col as f64 + 0.5) * out.cell_size_x;
1729 if let Some(ring) = &footprint_ring {
1730 if !point_in_polygon(x, y, ring) {
1731 continue;
1732 }
1733 }
1734 batch_coords.push((x, y));
1735 batch_cols.push(col as usize);
1736 }
1737
1738 let errors = dst_crs.transform_to_batch(&mut batch_coords, src_crs);
1742
1743 for (i, &col) in batch_cols.iter().enumerate() {
1744 if errors[i].is_some() {
1745 continue;
1746 }
1747 let (sx, sy) = batch_coords[i];
1748 for band in 0..out.bands as isize {
1749 if let Some(v) = self.sample_world(
1750 band,
1751 sx,
1752 sy,
1753 options.resample,
1754 options.nodata_policy,
1755 ) {
1756 row_values[band as usize * out.cols + col] = Some(v);
1757 }
1758 }
1759 }
1760
1761 if let Some(progress_cb) = progress {
1762 let done = completed_rows.fetch_add(1, Ordering::Relaxed) + 1;
1763 progress_cb(done as f64 / total_rows as f64);
1764 }
1765
1766 row_values
1767 })
1768 .collect();
1769
1770 for (row, row_values) in rows_data.into_iter().enumerate() {
1771 let row = row as isize;
1772 for band in 0..out.bands {
1773 for col in 0..out.cols {
1774 if let Some(v) = row_values[band * out.cols + col] {
1775 out.set_unchecked(band as isize, row, col as isize, v);
1776 }
1777 }
1778 }
1779 }
1780
1781 if let Some(progress_cb) = progress {
1782 progress_cb(1.0);
1783 }
1784
1785 Ok(out)
1786 }
1787
1788 pub fn sample_world(
1790 &self,
1791 band: isize,
1792 x: f64,
1793 y: f64,
1794 method: ResampleMethod,
1795 nodata_policy: NodataPolicy,
1796 ) -> Option<f64> {
1797 let col_f = (x - self.x_min) / self.cell_size_x - 0.5;
1798 let row_f = (self.y_max() - y) / self.cell_size_y - 0.5;
1799 match method {
1800 ResampleMethod::Nearest => self.sample_nearest_pixel(band, col_f, row_f),
1801 ResampleMethod::Bilinear => match nodata_policy {
1802 NodataPolicy::Strict => self.sample_bilinear_strict_pixel(band, col_f, row_f),
1803 NodataPolicy::PartialKernel => {
1804 self.sample_bilinear_partial_pixel(band, col_f, row_f)
1805 }
1806 NodataPolicy::Fill => self
1807 .sample_bilinear_strict_pixel(band, col_f, row_f)
1808 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
1809 },
1810 ResampleMethod::Cubic => match nodata_policy {
1811 NodataPolicy::Strict => self.sample_cubic_strict_pixel(band, col_f, row_f),
1812 NodataPolicy::PartialKernel => self.sample_cubic_partial_pixel(band, col_f, row_f),
1813 NodataPolicy::Fill => self
1814 .sample_cubic_strict_pixel(band, col_f, row_f)
1815 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
1816 },
1817 ResampleMethod::Lanczos => match nodata_policy {
1818 NodataPolicy::Strict => self.sample_lanczos_strict_pixel(band, col_f, row_f),
1819 NodataPolicy::PartialKernel => {
1820 self.sample_lanczos_partial_pixel(band, col_f, row_f)
1821 }
1822 NodataPolicy::Fill => self
1823 .sample_lanczos_strict_pixel(band, col_f, row_f)
1824 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
1825 },
1826 ResampleMethod::Average => self.sample_window_stat_pixel(
1827 band,
1828 col_f,
1829 row_f,
1830 WindowStat::Mean,
1831 nodata_policy,
1832 ),
1833 ResampleMethod::Min => self.sample_window_stat_pixel(
1834 band,
1835 col_f,
1836 row_f,
1837 WindowStat::Min,
1838 nodata_policy,
1839 ),
1840 ResampleMethod::Max => self.sample_window_stat_pixel(
1841 band,
1842 col_f,
1843 row_f,
1844 WindowStat::Max,
1845 nodata_policy,
1846 ),
1847 ResampleMethod::Mode => self.sample_window_stat_pixel(
1848 band,
1849 col_f,
1850 row_f,
1851 WindowStat::Mode,
1852 nodata_policy,
1853 ),
1854 ResampleMethod::Median => self.sample_window_stat_pixel(
1855 band,
1856 col_f,
1857 row_f,
1858 WindowStat::Median,
1859 nodata_policy,
1860 ),
1861 ResampleMethod::StdDev => self.sample_window_stat_pixel(
1862 band,
1863 col_f,
1864 row_f,
1865 WindowStat::StdDev,
1866 nodata_policy,
1867 ),
1868 }
1869 }
1870
1871 fn sample_window_stat_pixel(
1872 &self,
1873 band: isize,
1874 col_f: f64,
1875 row_f: f64,
1876 stat: WindowStat,
1877 nodata_policy: NodataPolicy,
1878 ) -> Option<f64> {
1879 if !col_f.is_finite() || !row_f.is_finite() {
1880 return None;
1881 }
1882
1883 let center_col = col_f.round() as isize;
1884 let center_row = row_f.round() as isize;
1885 let mut values = Vec::with_capacity(9);
1886 let mut valid_count = 0usize;
1887
1888 for dy in -1..=1 {
1889 for dx in -1..=1 {
1890 let c = center_col + dx;
1891 let r = center_row + dy;
1892 if c < 0 || r < 0 || c >= self.cols as isize || r >= self.rows as isize {
1893 continue;
1894 }
1895 if let Some(v) = self.get_opt(band, r, c) {
1896 values.push(v);
1897 valid_count += 1;
1898 }
1899 }
1900 }
1901
1902 match nodata_policy {
1903 NodataPolicy::Strict if valid_count < 9 => None,
1904 NodataPolicy::PartialKernel | NodataPolicy::Strict => {
1905 reduce_window_values(&values, stat)
1906 }
1907 NodataPolicy::Fill => reduce_window_values(&values, stat)
1908 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
1909 }
1910 }
1911
1912 fn sample_nearest_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
1913 if !col_f.is_finite() || !row_f.is_finite() {
1914 return None;
1915 }
1916 let col = col_f.round() as isize;
1917 let row = row_f.round() as isize;
1918 self.get_opt(band, row, col)
1919 }
1920
1921 fn sample_bilinear_strict_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
1922 if !col_f.is_finite() || !row_f.is_finite() {
1923 return None;
1924 }
1925 let c0 = col_f.floor() as isize;
1926 let r0 = row_f.floor() as isize;
1927 let c1 = c0 + 1;
1928 let r1 = r0 + 1;
1929 if c0 < 0 || r0 < 0 || c1 >= self.cols as isize || r1 >= self.rows as isize {
1930 return None;
1931 }
1932
1933 let tx = col_f - c0 as f64;
1934 let ty = row_f - r0 as f64;
1935
1936 if let Some(values) = self.data_f64() {
1937 return self.sample_bilinear_strict_simd_f64(values, band, r0, c0, tx, ty);
1938 }
1939 if let Some(values) = self.data_f32() {
1940 return self.sample_bilinear_strict_simd_f32(values, band, r0, c0, tx, ty);
1941 }
1942
1943 let q00 = self.get_opt(band, r0, c0)?;
1944 let q10 = self.get_opt(band, r0, c1)?;
1945 let q01 = self.get_opt(band, r1, c0)?;
1946 let q11 = self.get_opt(band, r1, c1)?;
1947
1948 let a = q00 * (1.0 - tx) + q10 * tx;
1949 let b = q01 * (1.0 - tx) + q11 * tx;
1950 Some(a * (1.0 - ty) + b * ty)
1951 }
1952
1953 #[inline]
1954 fn sample_bilinear_strict_simd_f64(
1955 &self,
1956 values: &[f64],
1957 band: isize,
1958 r0: isize,
1959 c0: isize,
1960 tx: f64,
1961 ty: f64,
1962 ) -> Option<f64> {
1963 let band_stride = self.rows * self.cols;
1964 let base = band as usize * band_stride + r0 as usize * self.cols + c0 as usize;
1965 let q00 = values[base];
1966 let q10 = values[base + 1];
1967 let q01 = values[base + self.cols];
1968 let q11 = values[base + self.cols + 1];
1969
1970 if self.is_nodata(q00)
1971 || self.is_nodata(q10)
1972 || self.is_nodata(q01)
1973 || self.is_nodata(q11)
1974 {
1975 return None;
1976 }
1977
1978 let weights = f64x4::new([
1979 (1.0 - tx) * (1.0 - ty),
1980 tx * (1.0 - ty),
1981 (1.0 - tx) * ty,
1982 tx * ty,
1983 ]);
1984 let vals = f64x4::new([q00, q10, q01, q11]);
1985 let weighted = <[f64; 4]>::from(vals * weights);
1986 Some(weighted.into_iter().sum())
1987 }
1988
1989 #[inline]
1990 fn sample_bilinear_strict_simd_f32(
1991 &self,
1992 values: &[f32],
1993 band: isize,
1994 r0: isize,
1995 c0: isize,
1996 tx: f64,
1997 ty: f64,
1998 ) -> Option<f64> {
1999 let band_stride = self.rows * self.cols;
2000 let base = band as usize * band_stride + r0 as usize * self.cols + c0 as usize;
2001 let q00 = values[base] as f64;
2002 let q10 = values[base + 1] as f64;
2003 let q01 = values[base + self.cols] as f64;
2004 let q11 = values[base + self.cols + 1] as f64;
2005
2006 if self.is_nodata(q00)
2007 || self.is_nodata(q10)
2008 || self.is_nodata(q01)
2009 || self.is_nodata(q11)
2010 {
2011 return None;
2012 }
2013
2014 let weights = f64x4::new([
2015 (1.0 - tx) * (1.0 - ty),
2016 tx * (1.0 - ty),
2017 (1.0 - tx) * ty,
2018 tx * ty,
2019 ]);
2020 let vals = f64x4::new([q00, q10, q01, q11]);
2021 let weighted = <[f64; 4]>::from(vals * weights);
2022 Some(weighted.into_iter().sum())
2023 }
2024
2025 fn sample_bilinear_partial_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2026 if !col_f.is_finite() || !row_f.is_finite() {
2027 return None;
2028 }
2029 let c0 = col_f.floor() as isize;
2030 let r0 = row_f.floor() as isize;
2031 let c1 = c0 + 1;
2032 let r1 = r0 + 1;
2033
2034 let tx = col_f - c0 as f64;
2035 let ty = row_f - r0 as f64;
2036
2037 let neighbors = [
2038 (c0, r0, (1.0 - tx) * (1.0 - ty)),
2039 (c1, r0, tx * (1.0 - ty)),
2040 (c0, r1, (1.0 - tx) * ty),
2041 (c1, r1, tx * ty),
2042 ];
2043
2044 let mut sum = 0.0;
2045 let mut wsum = 0.0;
2046 for (c, r, w) in neighbors {
2047 if w <= 0.0 || c < 0 || r < 0 || c >= self.cols as isize || r >= self.rows as isize {
2048 continue;
2049 }
2050 if let Some(v) = self.get_opt(band, r, c) {
2051 sum += v * w;
2052 wsum += w;
2053 }
2054 }
2055
2056 if wsum > 0.0 {
2057 Some(sum / wsum)
2058 } else {
2059 None
2060 }
2061 }
2062
2063 fn sample_cubic_strict_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2064 if !col_f.is_finite() || !row_f.is_finite() {
2065 return None;
2066 }
2067 let c1 = col_f.floor() as isize;
2068 let r1 = row_f.floor() as isize;
2069 if c1 - 1 < 0
2070 || r1 - 1 < 0
2071 || c1 + 2 >= self.cols as isize
2072 || r1 + 2 >= self.rows as isize
2073 {
2074 return None;
2075 }
2076
2077 let tx = col_f - c1 as f64;
2078 let ty = row_f - r1 as f64;
2079 let wx = cubic_bspline_weights(tx);
2080 let wy = cubic_bspline_weights(ty);
2081
2082 self.sample_cubic_simd_kernel(&wx, &wy, band, c1 - 1, r1 - 1)
2084 }
2085
2086 #[inline]
2089 fn sample_cubic_simd_kernel(
2090 &self,
2091 wx: &[f64; 4],
2092 wy: &[f64; 4],
2093 band: isize,
2094 c_start: isize,
2095 r_start: isize,
2096 ) -> Option<f64> {
2097 let mut row_sums = [0.0_f64; 4];
2100
2101 for (j, _wyj) in wy.iter().enumerate() {
2102 let rr = r_start + j as isize;
2103 let mut row_sum = 0.0;
2104
2105 for (i, wxi) in wx.iter().enumerate() {
2107 let cc = c_start + i as isize;
2108 let v = self.get_opt(band, rr, cc)?;
2109 row_sum += v * *wxi;
2110 }
2111
2112 row_sums[j] = row_sum;
2113 }
2114
2115 let mut sum = 0.0;
2117 for (j, wyj) in wy.iter().enumerate() {
2118 sum += row_sums[j] * *wyj;
2119 }
2120
2121 Some(sum)
2122 }
2123
2124 fn sample_cubic_partial_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2125 if !col_f.is_finite() || !row_f.is_finite() {
2126 return None;
2127 }
2128 let c1 = col_f.floor() as isize;
2129 let r1 = row_f.floor() as isize;
2130 let tx = col_f - c1 as f64;
2131 let ty = row_f - r1 as f64;
2132 let wx = cubic_bspline_weights(tx);
2133 let wy = cubic_bspline_weights(ty);
2134
2135 let mut sum = 0.0;
2136 let mut wsum = 0.0;
2137
2138 for (j, wyj) in wy.iter().enumerate() {
2139 let rr = clamp_isize(r1 + j as isize - 1, 0, self.rows as isize - 1);
2140 if *wyj <= 0.0 {
2141 continue;
2142 }
2143 for (i, wxi) in wx.iter().enumerate() {
2144 let cc = clamp_isize(c1 + i as isize - 1, 0, self.cols as isize - 1);
2145 let w = *wxi * *wyj;
2146 if w <= 0.0 {
2147 continue;
2148 }
2149 if let Some(v) = self.get_opt(band, rr, cc) {
2150 sum += v * w;
2151 wsum += w;
2152 }
2153 }
2154 }
2155
2156 if wsum > 0.0 {
2157 Some(sum / wsum)
2158 } else {
2159 None
2160 }
2161 }
2162
2163 fn sample_lanczos_strict_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2164 if !col_f.is_finite() || !row_f.is_finite() {
2165 return None;
2166 }
2167
2168 let c0 = col_f.floor() as isize;
2169 let r0 = row_f.floor() as isize;
2170 if c0 - 2 < 0
2171 || r0 - 2 < 0
2172 || c0 + 3 >= self.cols as isize
2173 || r0 + 3 >= self.rows as isize
2174 {
2175 return None;
2176 }
2177
2178 let wx = lanczos3_weights(col_f, c0);
2179 let wy = lanczos3_weights(row_f, r0);
2180
2181 if let Some(values) = self.data_f64() {
2182 return self.sample_lanczos_strict_simd_f64(values, band, c0, r0, &wx, &wy);
2183 }
2184 if let Some(values) = self.data_f32() {
2185 return self.sample_lanczos_strict_simd_f32(values, band, c0, r0, &wx, &wy);
2186 }
2187
2188 let mut sum = 0.0;
2189 let mut wsum = 0.0;
2190 for (j, wyj) in wy.iter().enumerate() {
2191 let rr = r0 + j as isize - 2;
2192 for (i, wxi) in wx.iter().enumerate() {
2193 let cc = c0 + i as isize - 2;
2194 let v = self.get_opt(band, rr, cc)?;
2195 let w = *wxi * *wyj;
2196 sum += v * w;
2197 wsum += w;
2198 }
2199 }
2200
2201 if wsum.abs() > 1e-12 {
2202 Some(sum / wsum)
2203 } else {
2204 None
2205 }
2206 }
2207
2208 #[inline]
2209 fn sample_lanczos_strict_simd_f64(
2210 &self,
2211 values: &[f64],
2212 band: isize,
2213 c0: isize,
2214 r0: isize,
2215 wx: &[f64; 6],
2216 wy: &[f64; 6],
2217 ) -> Option<f64> {
2218 let band_stride = self.rows * self.cols;
2219 let base = band as usize * band_stride;
2220
2221 let wx0 = f64x4::new([wx[0], wx[1], wx[2], wx[3]]);
2222 let wx1 = f64x4::new([wx[4], wx[5], 0.0, 0.0]);
2223 let wx_sum: f64 = wx.iter().copied().sum();
2224
2225 let mut sum = 0.0;
2226 let mut wsum = 0.0;
2227 for (j, wyj) in wy.iter().enumerate() {
2228 let rr = (r0 + j as isize - 2) as usize;
2229 let cc = (c0 - 2) as usize;
2230 let row_offset = base + rr * self.cols + cc;
2231
2232 let v = [
2233 values[row_offset],
2234 values[row_offset + 1],
2235 values[row_offset + 2],
2236 values[row_offset + 3],
2237 values[row_offset + 4],
2238 values[row_offset + 5],
2239 ];
2240 if v.into_iter().any(|cell| self.is_nodata(cell)) {
2241 return None;
2242 }
2243
2244 let v0 = f64x4::new([v[0], v[1], v[2], v[3]]);
2245 let v1 = f64x4::new([v[4], v[5], 0.0, 0.0]);
2246 let d0 = <[f64; 4]>::from(v0 * wx0);
2247 let d1 = <[f64; 4]>::from(v1 * wx1);
2248 let row_sum = d0.into_iter().sum::<f64>() + d1.into_iter().sum::<f64>();
2249 sum += row_sum * *wyj;
2250 wsum += wx_sum * *wyj;
2251 }
2252
2253 if wsum.abs() > 1e-12 {
2254 Some(sum / wsum)
2255 } else {
2256 None
2257 }
2258 }
2259
2260 #[inline]
2261 fn sample_lanczos_strict_simd_f32(
2262 &self,
2263 values: &[f32],
2264 band: isize,
2265 c0: isize,
2266 r0: isize,
2267 wx: &[f64; 6],
2268 wy: &[f64; 6],
2269 ) -> Option<f64> {
2270 let band_stride = self.rows * self.cols;
2271 let base = band as usize * band_stride;
2272
2273 let wx0 = f64x4::new([wx[0], wx[1], wx[2], wx[3]]);
2274 let wx1 = f64x4::new([wx[4], wx[5], 0.0, 0.0]);
2275 let wx_sum: f64 = wx.iter().copied().sum();
2276
2277 let mut sum = 0.0;
2278 let mut wsum = 0.0;
2279 for (j, wyj) in wy.iter().enumerate() {
2280 let rr = (r0 + j as isize - 2) as usize;
2281 let cc = (c0 - 2) as usize;
2282 let row_offset = base + rr * self.cols + cc;
2283
2284 let v = [
2285 values[row_offset] as f64,
2286 values[row_offset + 1] as f64,
2287 values[row_offset + 2] as f64,
2288 values[row_offset + 3] as f64,
2289 values[row_offset + 4] as f64,
2290 values[row_offset + 5] as f64,
2291 ];
2292 if v.into_iter().any(|cell| self.is_nodata(cell)) {
2293 return None;
2294 }
2295
2296 let v0 = f64x4::new([v[0], v[1], v[2], v[3]]);
2297 let v1 = f64x4::new([v[4], v[5], 0.0, 0.0]);
2298 let d0 = <[f64; 4]>::from(v0 * wx0);
2299 let d1 = <[f64; 4]>::from(v1 * wx1);
2300 let row_sum = d0.into_iter().sum::<f64>() + d1.into_iter().sum::<f64>();
2301 sum += row_sum * *wyj;
2302 wsum += wx_sum * *wyj;
2303 }
2304
2305 if wsum.abs() > 1e-12 {
2306 Some(sum / wsum)
2307 } else {
2308 None
2309 }
2310 }
2311
2312 fn sample_lanczos_partial_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2313 if !col_f.is_finite() || !row_f.is_finite() {
2314 return None;
2315 }
2316
2317 let c0 = col_f.floor() as isize;
2318 let r0 = row_f.floor() as isize;
2319 let wx = lanczos3_weights(col_f, c0);
2320 let wy = lanczos3_weights(row_f, r0);
2321
2322 let mut sum = 0.0;
2323 let mut wsum = 0.0;
2324
2325 for (j, wyj) in wy.iter().enumerate() {
2326 let rr = clamp_isize(r0 + j as isize - 2, 0, self.rows as isize - 1);
2327 for (i, wxi) in wx.iter().enumerate() {
2328 let cc = clamp_isize(c0 + i as isize - 2, 0, self.cols as isize - 1);
2329 let w = *wxi * *wyj;
2330 if w == 0.0 {
2331 continue;
2332 }
2333 if let Some(v) = self.get_opt(band, rr, cc) {
2334 sum += v * w;
2335 wsum += w;
2336 }
2337 }
2338 }
2339
2340 if wsum.abs() > 1e-12 {
2341 Some(sum / wsum)
2342 } else {
2343 None
2344 }
2345 }
2346
2347 fn stats_accumulator_range_with_mode(
2350 &self,
2351 start: usize,
2352 end: usize,
2353 mode: StatisticsComputationMode,
2354 ) -> StatsAccumulator {
2355 match mode {
2356 StatisticsComputationMode::Auto | StatisticsComputationMode::Simd => {
2357 self.stats_accumulator_range_simd(start, end)
2358 }
2359 StatisticsComputationMode::Scalar => self.stats_accumulator_range_scalar(start, end),
2360 }
2361 }
2362
2363 fn stats_accumulator_range_scalar(&self, start: usize, end: usize) -> StatsAccumulator {
2364 let mut accumulator = StatsAccumulator::default();
2365
2366 for idx in start..end {
2367 let value = self.data.get_f64(idx);
2368 if self.is_nodata(value) {
2369 accumulator.nodata_count += 1;
2370 } else {
2371 accumulator.min = accumulator.min.min(value);
2372 accumulator.max = accumulator.max.max(value);
2373 accumulator.sum += value;
2374 accumulator.sum_sq += value * value;
2375 accumulator.valid_count += 1;
2376 }
2377 }
2378
2379 accumulator
2380 }
2381
2382 fn stats_accumulator_range_simd(&self, start: usize, end: usize) -> StatsAccumulator {
2386 if let Some(values) = self.data_f64() {
2387 return self.stats_accumulator_range_simd_f64(values, start, end);
2388 }
2389 if let Some(values) = self.data_f32() {
2390 return self.stats_accumulator_range_simd_f32(values, start, end);
2391 }
2392
2393 self.stats_accumulator_range_scalar(start, end)
2394 }
2395
2396 fn stats_accumulator_range_simd_f64(
2397 &self,
2398 values: &[f64],
2399 start: usize,
2400 end: usize,
2401 ) -> StatsAccumulator {
2402 let mut accumulator = StatsAccumulator::default();
2403 let nd = self.nodata;
2404
2405 let simd_end = start + ((end - start) / 4) * 4;
2406 let mut simd_min = f64x4::splat(f64::INFINITY);
2407 let mut simd_max = f64x4::splat(f64::NEG_INFINITY);
2408 let mut simd_sum = f64x4::splat(0.0);
2409 let mut simd_sum_sq = f64x4::splat(0.0);
2410 let zero_v = f64x4::splat(0.0);
2411 let nd_v = f64x4::splat(nd);
2412 let inf_v = f64x4::splat(f64::INFINITY);
2413 let neg_inf_v = f64x4::splat(f64::NEG_INFINITY);
2414
2415 let mut idx = start;
2416 while idx < simd_end {
2417 let chunk = &values[idx..idx + 4];
2418 let values = f64x4::new([chunk[0], chunk[1], chunk[2], chunk[3]]);
2419
2420 let not_nodata = values.simd_ne(nd_v);
2421
2422 let values_for_min = not_nodata.blend(values, inf_v);
2423 let values_for_max = not_nodata.blend(values, neg_inf_v);
2424 simd_min = simd_min.min(values_for_min);
2425 simd_max = simd_max.max(values_for_max);
2426
2427 let masked_values = not_nodata.blend(values, zero_v);
2428 simd_sum = simd_sum + masked_values;
2429 simd_sum_sq = simd_sum_sq + masked_values * masked_values;
2430
2431 for &val in chunk {
2432 if val == nd {
2433 accumulator.nodata_count += 1;
2434 } else {
2435 accumulator.valid_count += 1;
2436 }
2437 }
2438
2439 idx += 4;
2440 }
2441
2442 let sum_arr = <[f64; 4]>::from(simd_sum);
2443 let sum_sq_arr = <[f64; 4]>::from(simd_sum_sq);
2444 let min_arr = <[f64; 4]>::from(simd_min);
2445 let max_arr = <[f64; 4]>::from(simd_max);
2446
2447 for i in 0..4 {
2448 accumulator.sum += sum_arr[i];
2449 accumulator.sum_sq += sum_sq_arr[i];
2450 accumulator.min = accumulator.min.min(min_arr[i]);
2451 accumulator.max = accumulator.max.max(max_arr[i]);
2452 }
2453
2454 for &value in &values[simd_end..end] {
2455 if value == nd {
2456 accumulator.nodata_count += 1;
2457 } else {
2458 accumulator.min = accumulator.min.min(value);
2459 accumulator.max = accumulator.max.max(value);
2460 accumulator.sum += value;
2461 accumulator.sum_sq += value * value;
2462 accumulator.valid_count += 1;
2463 }
2464 }
2465
2466 accumulator
2467 }
2468
2469 fn stats_accumulator_range_simd_f32(
2470 &self,
2471 values: &[f32],
2472 start: usize,
2473 end: usize,
2474 ) -> StatsAccumulator {
2475 let mut accumulator = StatsAccumulator::default();
2476 let nd = self.nodata as f32;
2477
2478 let simd_end = start + ((end - start) / 4) * 4;
2479 let mut simd_min = f64x4::splat(f64::INFINITY);
2480 let mut simd_max = f64x4::splat(f64::NEG_INFINITY);
2481 let mut simd_sum = f64x4::splat(0.0);
2482 let mut simd_sum_sq = f64x4::splat(0.0);
2483 let zero_v = f64x4::splat(0.0);
2484 let nd_v = f64x4::splat(nd as f64);
2485 let inf_v = f64x4::splat(f64::INFINITY);
2486 let neg_inf_v = f64x4::splat(f64::NEG_INFINITY);
2487
2488 let mut idx = start;
2489 while idx < simd_end {
2490 let chunk = &values[idx..idx + 4];
2491 let values = f64x4::new([
2492 chunk[0] as f64,
2493 chunk[1] as f64,
2494 chunk[2] as f64,
2495 chunk[3] as f64,
2496 ]);
2497
2498 let not_nodata = values.simd_ne(nd_v);
2499 let values_for_min = not_nodata.blend(values, inf_v);
2500 let values_for_max = not_nodata.blend(values, neg_inf_v);
2501 simd_min = simd_min.min(values_for_min);
2502 simd_max = simd_max.max(values_for_max);
2503
2504 let masked_values = not_nodata.blend(values, zero_v);
2505 simd_sum = simd_sum + masked_values;
2506 simd_sum_sq = simd_sum_sq + masked_values * masked_values;
2507
2508 for &val in chunk {
2509 if val == nd {
2510 accumulator.nodata_count += 1;
2511 } else {
2512 accumulator.valid_count += 1;
2513 }
2514 }
2515
2516 idx += 4;
2517 }
2518
2519 let sum_arr = <[f64; 4]>::from(simd_sum);
2520 let sum_sq_arr = <[f64; 4]>::from(simd_sum_sq);
2521 let min_arr = <[f64; 4]>::from(simd_min);
2522 let max_arr = <[f64; 4]>::from(simd_max);
2523
2524 for i in 0..4 {
2525 accumulator.sum += sum_arr[i];
2526 accumulator.sum_sq += sum_sq_arr[i];
2527 accumulator.min = accumulator.min.min(min_arr[i]);
2528 accumulator.max = accumulator.max.max(max_arr[i]);
2529 }
2530
2531 for &value in &values[simd_end..end] {
2532 if value == nd {
2533 accumulator.nodata_count += 1;
2534 } else {
2535 let value = value as f64;
2536 accumulator.min = accumulator.min.min(value);
2537 accumulator.max = accumulator.max.max(value);
2538 accumulator.sum += value;
2539 accumulator.sum_sq += value * value;
2540 accumulator.valid_count += 1;
2541 }
2542 }
2543
2544 accumulator
2545 }
2546
2547 fn statistics_for_index_range_with_mode(
2548 &self,
2549 start: usize,
2550 end: usize,
2551 mode: StatisticsComputationMode,
2552 ) -> Statistics {
2553 const PARALLEL_THRESHOLD: usize = 65_536;
2554 const CHUNK_SIZE: usize = 16_384;
2555
2556 let span = end.saturating_sub(start);
2557 if span == 0 {
2558 return Statistics {
2559 min: 0.0,
2560 max: 0.0,
2561 mean: 0.0,
2562 std_dev: 0.0,
2563 valid_count: 0,
2564 nodata_count: 0,
2565 };
2566 }
2567
2568 let total = if span < PARALLEL_THRESHOLD {
2569 self.stats_accumulator_range_with_mode(start, end, mode)
2570 } else {
2571 let chunk_starts: Vec<usize> = (start..end).step_by(CHUNK_SIZE).collect();
2572 let partials: Vec<StatsAccumulator> = chunk_starts
2573 .into_par_iter()
2574 .map(|chunk_start| {
2575 let chunk_end = (chunk_start + CHUNK_SIZE).min(end);
2576 self.stats_accumulator_range_with_mode(chunk_start, chunk_end, mode)
2577 })
2578 .collect();
2579
2580 partials.into_iter().fold(StatsAccumulator::default(), |mut lhs, rhs| {
2581 lhs.merge(rhs);
2582 lhs
2583 })
2584 };
2585
2586 total.to_statistics()
2587 }
2588
2589 pub fn statistics(&self) -> Statistics {
2591 self.statistics_with_mode(StatisticsComputationMode::Auto)
2592 }
2593
2594 pub fn statistics_with_mode(&self, mode: StatisticsComputationMode) -> Statistics {
2596 self.statistics_for_index_range_with_mode(0, self.data.len(), mode)
2597 }
2598
2599 pub fn statistics_band(&self, band: isize) -> Result<Statistics> {
2601 self.statistics_band_with_mode(band, StatisticsComputationMode::Auto)
2602 }
2603
2604 pub fn statistics_band_with_mode(
2606 &self,
2607 band: isize,
2608 mode: StatisticsComputationMode,
2609 ) -> Result<Statistics> {
2610 if band < 0 || band >= self.bands as isize {
2611 return Err(RasterError::OutOfBounds {
2612 band,
2613 col: 0,
2614 row: 0,
2615 bands: self.bands,
2616 cols: self.cols,
2617 rows: self.rows,
2618 });
2619 }
2620
2621 let band = band as usize;
2622 let band_stride = self.rows * self.cols;
2623 let start = band * band_stride;
2624 let end = start + band_stride;
2625
2626 Ok(self.statistics_for_index_range_with_mode(start, end, mode))
2627 }
2628
2629 #[inline]
2633 pub fn band_slice(&self, band: isize) -> Vec<f64> {
2634 if band < 0 || band >= self.bands as isize {
2635 return Vec::new();
2636 }
2637 let band_stride = self.rows * self.cols;
2638 let start = band as usize * band_stride;
2639 (start..start + band_stride)
2640 .map(|i| self.data.get_f64(i))
2641 .collect()
2642 }
2643
2644 pub fn set_band_slice(&mut self, band: isize, values: &[f64]) -> Result<()> {
2646 let expected = self.rows * self.cols;
2647 if values.len() != expected {
2648 return Err(RasterError::InvalidDimensions {
2649 cols: self.cols,
2650 rows: values.len(),
2651 });
2652 }
2653 if band < 0 || band >= self.bands as isize {
2654 return Err(RasterError::OutOfBounds {
2655 band,
2656 col: 0,
2657 row: 0,
2658 bands: self.bands,
2659 cols: self.cols,
2660 rows: self.rows,
2661 });
2662 }
2663 let band_stride = self.rows * self.cols;
2664 let start = band as usize * band_stride;
2665 for (i, v) in values.iter().copied().enumerate() {
2666 self.data.set_f64(start + i, v);
2667 }
2668 Ok(())
2669 }
2670
2671 #[inline]
2673 pub fn row_slice(&self, band: isize, row: isize) -> Vec<f64> {
2674 if band < 0 || band >= self.bands as isize || row < 0 || row >= self.rows as isize {
2675 return Vec::new();
2676 }
2677 let band_stride = self.rows * self.cols;
2678 let start = band as usize * band_stride + row as usize * self.cols;
2679 (start..start + self.cols).map(|i| self.data.get_f64(i)).collect()
2680 }
2681
2682 pub fn set_row_slice(&mut self, band: isize, row: isize, values: &[f64]) -> Result<()> {
2684 if values.len() != self.cols {
2685 return Err(RasterError::InvalidDimensions { cols: values.len(), rows: self.rows });
2686 }
2687 if band < 0 || band >= self.bands as isize || row < 0 || row >= self.rows as isize {
2688 return Err(RasterError::OutOfBounds {
2689 band,
2690 col: 0,
2691 row,
2692 bands: self.bands,
2693 cols: self.cols,
2694 rows: self.rows,
2695 });
2696 }
2697 let band_stride = self.rows * self.cols;
2698 let start = band as usize * band_stride + row as usize * self.cols;
2699 for (i, v) in values.iter().copied().enumerate() {
2700 self.data.set_f64(start + i, v);
2701 }
2702 Ok(())
2703 }
2704
2705 pub fn iter_valid(&self) -> impl Iterator<Item = (isize, isize, isize, f64)> + '_ {
2707 self.data.iter_f64().enumerate().filter_map(move |(idx, v)| {
2708 if self.is_nodata(v) {
2709 None
2710 } else {
2711 let band_stride = self.rows * self.cols;
2712 let band = idx / band_stride;
2713 let rem = idx % band_stride;
2714 let row = rem / self.cols;
2715 let col = rem % self.cols;
2716 Some((band as isize, row as isize, col as isize, v))
2717 }
2718 })
2719 }
2720
2721 pub fn iter_valid_band(
2723 &self,
2724 band: isize,
2725 ) -> Result<Box<dyn Iterator<Item = (isize, isize, f64)> + '_>> {
2726 if band < 0 || band >= self.bands as isize {
2727 return Err(RasterError::OutOfBounds {
2728 band,
2729 col: 0,
2730 row: 0,
2731 bands: self.bands,
2732 cols: self.cols,
2733 rows: self.rows,
2734 });
2735 }
2736
2737 let b = band as usize;
2738 let band_stride = self.rows * self.cols;
2739 let start = b * band_stride;
2740 Ok(Box::new((0..band_stride).filter_map(move |i| {
2741 let v = self.data.get_f64(start + i);
2742 if self.is_nodata(v) {
2743 None
2744 } else {
2745 let row = i / self.cols;
2746 let col = i % self.cols;
2747 Some((row as isize, col as isize, v))
2748 }
2749 })))
2750 }
2751
2752 pub fn iter_band_rows(&self, band: isize) -> Result<Box<dyn Iterator<Item = Vec<f64>> + '_>> {
2754 if band < 0 || band >= self.bands as isize {
2755 return Err(RasterError::OutOfBounds {
2756 band,
2757 col: 0,
2758 row: 0,
2759 bands: self.bands,
2760 cols: self.cols,
2761 rows: self.rows,
2762 });
2763 }
2764
2765 Ok(Box::new((0..self.rows).map(move |row| self.row_slice(band, row as isize))))
2766 }
2767
2768 pub fn for_each_band_row_mut<F>(&mut self, band: isize, mut f: F) -> Result<()>
2773 where
2774 F: FnMut(isize, RasterRowMut<'_>),
2775 {
2776 if band < 0 || band >= self.bands as isize {
2777 return Err(RasterError::OutOfBounds {
2778 band,
2779 col: 0,
2780 row: 0,
2781 bands: self.bands,
2782 cols: self.cols,
2783 rows: self.rows,
2784 });
2785 }
2786
2787 let b = band as usize;
2788 let band_stride = self.rows * self.cols;
2789 let start = b * band_stride;
2790 let end = start + band_stride;
2791
2792 match &mut self.data {
2793 RasterData::U8(v) => {
2794 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2795 f(row as isize, RasterRowMut::U8(chunk));
2796 }
2797 }
2798 RasterData::I8(v) => {
2799 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2800 f(row as isize, RasterRowMut::I8(chunk));
2801 }
2802 }
2803 RasterData::U16(v) => {
2804 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2805 f(row as isize, RasterRowMut::U16(chunk));
2806 }
2807 }
2808 RasterData::I16(v) => {
2809 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2810 f(row as isize, RasterRowMut::I16(chunk));
2811 }
2812 }
2813 RasterData::U32(v) => {
2814 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2815 f(row as isize, RasterRowMut::U32(chunk));
2816 }
2817 }
2818 RasterData::I32(v) => {
2819 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2820 f(row as isize, RasterRowMut::I32(chunk));
2821 }
2822 }
2823 RasterData::U64(v) => {
2824 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2825 f(row as isize, RasterRowMut::U64(chunk));
2826 }
2827 }
2828 RasterData::I64(v) => {
2829 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2830 f(row as isize, RasterRowMut::I64(chunk));
2831 }
2832 }
2833 RasterData::F32(v) => {
2834 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2835 f(row as isize, RasterRowMut::F32(chunk));
2836 }
2837 }
2838 RasterData::F64(v) => {
2839 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
2840 f(row as isize, RasterRowMut::F64(chunk));
2841 }
2842 }
2843 }
2844
2845 Ok(())
2846 }
2847
2848 pub fn for_each_band_row<F>(&self, band: isize, mut f: F) -> Result<()>
2853 where
2854 F: FnMut(isize, RasterRowRef<'_>),
2855 {
2856 if band < 0 || band >= self.bands as isize {
2857 return Err(RasterError::OutOfBounds {
2858 band,
2859 col: 0,
2860 row: 0,
2861 bands: self.bands,
2862 cols: self.cols,
2863 rows: self.rows,
2864 });
2865 }
2866
2867 let b = band as usize;
2868 let band_stride = self.rows * self.cols;
2869 let start = b * band_stride;
2870 let end = start + band_stride;
2871
2872 match &self.data {
2873 RasterData::U8(v) => {
2874 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2875 f(row as isize, RasterRowRef::U8(chunk));
2876 }
2877 }
2878 RasterData::I8(v) => {
2879 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2880 f(row as isize, RasterRowRef::I8(chunk));
2881 }
2882 }
2883 RasterData::U16(v) => {
2884 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2885 f(row as isize, RasterRowRef::U16(chunk));
2886 }
2887 }
2888 RasterData::I16(v) => {
2889 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2890 f(row as isize, RasterRowRef::I16(chunk));
2891 }
2892 }
2893 RasterData::U32(v) => {
2894 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2895 f(row as isize, RasterRowRef::U32(chunk));
2896 }
2897 }
2898 RasterData::I32(v) => {
2899 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2900 f(row as isize, RasterRowRef::I32(chunk));
2901 }
2902 }
2903 RasterData::U64(v) => {
2904 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2905 f(row as isize, RasterRowRef::U64(chunk));
2906 }
2907 }
2908 RasterData::I64(v) => {
2909 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2910 f(row as isize, RasterRowRef::I64(chunk));
2911 }
2912 }
2913 RasterData::F32(v) => {
2914 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2915 f(row as isize, RasterRowRef::F32(chunk));
2916 }
2917 }
2918 RasterData::F64(v) => {
2919 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
2920 f(row as isize, RasterRowRef::F64(chunk));
2921 }
2922 }
2923 }
2924
2925 Ok(())
2926 }
2927
2928 pub fn fill(&mut self, value: f64) {
2932 for i in 0..self.data.len() {
2933 self.data.set_f64(i, value);
2934 }
2935 }
2936
2937 pub fn fill_nodata(&mut self) {
2939 let nd = self.nodata;
2940 self.fill(nd);
2941 }
2942
2943 pub fn map_valid<F: Fn(f64) -> f64>(&mut self, f: F) {
2945 let nd = self.nodata;
2946 let nodata_nan = nd.is_nan();
2947 for i in 0..self.data.len() {
2948 let v = self.data.get_f64(i);
2949 let is_nd = if nodata_nan { v.is_nan() } else { (v - nd).abs() < 1e-10 * nd.abs().max(1.0) };
2950 if !is_nd {
2951 self.data.set_f64(i, f(v));
2952 }
2953 }
2954 }
2955
2956 pub fn map_valid_band<F: Fn(f64) -> f64>(&mut self, band: isize, f: F) -> Result<()> {
2958 if band < 0 || band >= self.bands as isize {
2959 return Err(RasterError::OutOfBounds {
2960 band,
2961 col: 0,
2962 row: 0,
2963 bands: self.bands,
2964 cols: self.cols,
2965 rows: self.rows,
2966 });
2967 }
2968
2969 let nd = self.nodata;
2970 let nodata_nan = nd.is_nan();
2971 let band_stride = self.rows * self.cols;
2972 let start = band as usize * band_stride;
2973 let end = start + band_stride;
2974
2975 for i in start..end {
2976 let v = self.data.get_f64(i);
2977 let is_nd = if nodata_nan {
2978 v.is_nan()
2979 } else {
2980 (v - nd).abs() < 1e-10 * nd.abs().max(1.0)
2981 };
2982 if !is_nd {
2983 self.data.set_f64(i, f(v));
2984 }
2985 }
2986 Ok(())
2987 }
2988
2989 pub fn replace(&mut self, from: f64, to: f64) {
2991 for i in 0..self.data.len() {
2992 let v = self.data.get_f64(i);
2993 if (v - from).abs() < f64::EPSILON {
2994 self.data.set_f64(i, to);
2995 }
2996 }
2997 }
2998
2999 pub fn replace_band(&mut self, band: isize, from: f64, to: f64) -> Result<()> {
3001 if band < 0 || band >= self.bands as isize {
3002 return Err(RasterError::OutOfBounds {
3003 band,
3004 col: 0,
3005 row: 0,
3006 bands: self.bands,
3007 cols: self.cols,
3008 rows: self.rows,
3009 });
3010 }
3011
3012 let band_stride = self.rows * self.cols;
3013 let start = band as usize * band_stride;
3014 let end = start + band_stride;
3015 for i in start..end {
3016 let v = self.data.get_f64(i);
3017 if (v - from).abs() < f64::EPSILON {
3018 self.data.set_f64(i, to);
3019 }
3020 }
3021 Ok(())
3022 }
3023
3024 pub fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
3028 let path = path.as_ref().to_string_lossy().to_string();
3029 let fmt = RasterFormat::detect(&path)?;
3030 fmt.read(&path)
3031 }
3032
3033 pub fn read_with_format<P: AsRef<Path>>(path: P, fmt: RasterFormat) -> Result<Self> {
3035 let path = path.as_ref().to_string_lossy().to_string();
3036 fmt.read(&path)
3037 }
3038
3039 pub fn write<P: AsRef<Path>>(&self, path: P, fmt: RasterFormat) -> Result<()> {
3041 let path = path.as_ref().to_string_lossy().to_string();
3042 fmt.write(self, &path)
3043 }
3044
3045 pub fn write_auto<P: AsRef<Path>>(&self, path: P) -> Result<()> {
3047 let path = path.as_ref().to_string_lossy().to_string();
3048 let fmt = RasterFormat::detect(&path)?;
3049 fmt.write(self, &path)
3050 }
3051
3052 pub fn write_geotiff_with_options<P: AsRef<Path>>(
3054 &self,
3055 path: P,
3056 opts: &crate::formats::geotiff::GeoTiffWriteOptions,
3057 ) -> Result<()> {
3058 let path = path.as_ref().to_string_lossy().to_string();
3059 crate::formats::geotiff::write_with_options(self, &path, opts)
3060 }
3061
3062 pub fn write_cog<P: AsRef<Path>>(&self, path: P) -> Result<()> {
3070 let opts = crate::formats::geotiff::GeoTiffWriteOptions {
3071 compression: Some(crate::formats::geotiff::GeoTiffCompression::Deflate),
3072 bigtiff: Some(false),
3073 layout: Some(crate::formats::geotiff::GeoTiffLayout::Cog { tile_size: 512 }),
3074 };
3075 self.write_geotiff_with_options(path, &opts)
3076 }
3077
3078 pub fn write_cog_with_tile_size<P: AsRef<Path>>(&self, path: P, tile_size: u32) -> Result<()> {
3086 let opts = crate::formats::geotiff::GeoTiffWriteOptions {
3087 compression: Some(crate::formats::geotiff::GeoTiffCompression::Deflate),
3088 bigtiff: Some(false),
3089 layout: Some(crate::formats::geotiff::GeoTiffLayout::Cog { tile_size }),
3090 };
3091 self.write_geotiff_with_options(path, &opts)
3092 }
3093
3094 pub fn write_cog_with_options<P: AsRef<Path>>(
3102 &self,
3103 path: P,
3104 opts: &crate::formats::geotiff::CogWriteOptions,
3105 ) -> Result<()> {
3106 let geotiff_opts = crate::formats::geotiff::GeoTiffWriteOptions {
3107 compression: Some(
3108 opts.compression
3109 .unwrap_or(crate::formats::geotiff::GeoTiffCompression::Deflate),
3110 ),
3111 bigtiff: Some(opts.bigtiff.unwrap_or(false)),
3112 layout: Some(crate::formats::geotiff::GeoTiffLayout::Cog {
3113 tile_size: opts.tile_size.unwrap_or(512),
3114 }),
3115 };
3116 self.write_geotiff_with_options(path, &geotiff_opts)
3117 }
3118
3119 pub fn write_jpeg2000_with_options<P: AsRef<Path>>(
3121 &self,
3122 path: P,
3123 opts: &crate::formats::jpeg2000::Jpeg2000WriteOptions,
3124 ) -> Result<()> {
3125 let path = path.as_ref().to_string_lossy().to_string();
3126 crate::formats::jpeg2000::write_with_options(self, &path, opts)
3127 }
3128}
3129
3130fn cubic_bspline_weights(t: f64) -> [f64; 4] {
3131 let t = t.clamp(0.0, 1.0);
3132 let t2 = t * t;
3133 let t3 = t2 * t;
3134 [
3135 ((1.0 - t) * (1.0 - t) * (1.0 - t)) / 6.0,
3136 (3.0 * t3 - 6.0 * t2 + 4.0) / 6.0,
3137 (-3.0 * t3 + 3.0 * t2 + 3.0 * t + 1.0) / 6.0,
3138 t3 / 6.0,
3139 ]
3140}
3141
3142fn lanczos_kernel(x: f64, a: f64) -> f64 {
3143 if x.abs() < 1e-12 {
3144 return 1.0;
3145 }
3146 if x.abs() >= a {
3147 return 0.0;
3148 }
3149 let pix = std::f64::consts::PI * x;
3150 let pix_over_a = pix / a;
3151 (pix.sin() / pix) * (pix_over_a.sin() / pix_over_a)
3152}
3153
3154fn lanczos3_weights(sample_f: f64, floor_idx: isize) -> [f64; 6] {
3155 let mut w = [0.0_f64; 6];
3156 for (i, wi) in w.iter_mut().enumerate() {
3157 let idx = floor_idx + i as isize - 2;
3158 let dx = sample_f - idx as f64;
3159 *wi = lanczos_kernel(dx, 3.0);
3160 }
3161 w
3162}
3163
3164fn sample_extent_boundary_points(extent: Extent, samples_per_edge: usize) -> Vec<(f64, f64)> {
3165 let n = samples_per_edge.max(1);
3166 let mut pts = Vec::with_capacity(4 * n);
3167
3168 for i in 0..=n {
3170 let t = i as f64 / n as f64;
3171 let x = extent.x_min + t * (extent.x_max - extent.x_min);
3172 pts.push((x, extent.y_min));
3173 pts.push((x, extent.y_max));
3174 }
3175
3176 for j in 1..n {
3178 let t = j as f64 / n as f64;
3179 let y = extent.y_min + t * (extent.y_max - extent.y_min);
3180 pts.push((extent.x_min, y));
3181 pts.push((extent.x_max, y));
3182 }
3183
3184 pts
3185}
3186
3187fn sample_extent_boundary_ring(extent: Extent, samples_per_edge: usize) -> Vec<(f64, f64)> {
3188 let n = samples_per_edge.max(1);
3189 let mut ring = Vec::with_capacity(n * 4);
3190
3191 for i in 0..n {
3192 let t = if n == 1 {
3193 0.0
3194 } else {
3195 i as f64 / (n - 1) as f64
3196 };
3197 let x = extent.x_min + t * (extent.x_max - extent.x_min);
3198 ring.push((x, extent.y_min));
3199 }
3200
3201 for i in 0..n {
3202 let t = if n == 1 {
3203 0.0
3204 } else {
3205 i as f64 / (n - 1) as f64
3206 };
3207 let y = extent.y_min + t * (extent.y_max - extent.y_min);
3208 ring.push((extent.x_max, y));
3209 }
3210
3211 for i in 0..n {
3212 let t = if n == 1 {
3213 0.0
3214 } else {
3215 i as f64 / (n - 1) as f64
3216 };
3217 let x = extent.x_max - t * (extent.x_max - extent.x_min);
3218 ring.push((x, extent.y_max));
3219 }
3220
3221 for i in 0..n {
3222 let t = if n == 1 {
3223 0.0
3224 } else {
3225 i as f64 / (n - 1) as f64
3226 };
3227 let y = extent.y_max - t * (extent.y_max - extent.y_min);
3228 ring.push((extent.x_min, y));
3229 }
3230
3231 ring
3232}
3233
3234fn transformed_extent_from_boundary_samples(
3235 src_crs: &Crs,
3236 dst_crs: &Crs,
3237 src_extent: Extent,
3238 samples_per_edge: usize,
3239 dst_epsg: u32,
3240 antimeridian_policy: AntimeridianPolicy,
3241) -> Result<Extent> {
3242 let points = sample_extent_boundary_points(src_extent, samples_per_edge);
3243
3244 let mut tx_min = f64::INFINITY;
3245 let mut tx_max = f64::NEG_INFINITY;
3246 let mut ty_min = f64::INFINITY;
3247 let mut ty_max = f64::NEG_INFINITY;
3248 let mut tx_values = Vec::new();
3249 let mut valid = 0usize;
3250
3251 for (x, y) in points {
3252 let Ok((tx, ty)) = src_crs.transform_to(x, y, dst_crs) else {
3253 continue;
3254 };
3255 if !tx.is_finite() || !ty.is_finite() {
3256 continue;
3257 }
3258 tx_min = tx_min.min(tx);
3259 tx_max = tx_max.max(tx);
3260 ty_min = ty_min.min(ty);
3261 ty_max = ty_max.max(ty);
3262 tx_values.push(tx);
3263 valid += 1;
3264 }
3265
3266 if valid == 0 {
3267 return Err(RasterError::Other(format!(
3268 "failed to transform source extent boundary samples to EPSG:{}",
3269 dst_epsg
3270 )));
3271 }
3272
3273 if dst_epsg == 4326 {
3274 if let Some((x0, x1)) = antimeridian_aware_longitude_bounds(
3275 &tx_values,
3276 antimeridian_policy,
3277 ) {
3278 tx_min = x0;
3279 tx_max = x1;
3280 }
3281 }
3282
3283 Ok(Extent {
3284 x_min: tx_min,
3285 y_min: ty_min,
3286 x_max: tx_max,
3287 y_max: ty_max,
3288 })
3289}
3290
3291fn transformed_boundary_ring_samples(
3292 src_crs: &Crs,
3293 dst_crs: &Crs,
3294 src_extent: Extent,
3295 samples_per_edge: usize,
3296 dst_epsg: u32,
3297 antimeridian_policy: AntimeridianPolicy,
3298) -> Result<Vec<(f64, f64)>> {
3299 let ring = sample_extent_boundary_ring(src_extent, samples_per_edge);
3300 let mut transformed = Vec::with_capacity(ring.len());
3301
3302 for (x, y) in ring {
3303 let Ok((tx, ty)) = src_crs.transform_to(x, y, dst_crs) else {
3304 continue;
3305 };
3306 if tx.is_finite() && ty.is_finite() {
3307 transformed.push((tx, ty));
3308 }
3309 }
3310
3311 if transformed.len() < 3 {
3312 return Err(RasterError::Other(format!(
3313 "failed to build transformed boundary ring for EPSG:{}",
3314 dst_epsg
3315 )));
3316 }
3317
3318 if dst_epsg == 4326 && antimeridian_policy != AntimeridianPolicy::Linear {
3319 let lons: Vec<f64> = transformed.iter().map(|(x, _)| *x).collect();
3320 if let Some((base, _)) = minimal_wrapped_longitude_bounds(&lons) {
3321 for (x, _) in &mut transformed {
3322 let mut w = wrap_lon_360(*x);
3323 if w < base {
3324 w += 360.0;
3325 }
3326 *x = w;
3327 }
3328 }
3329 }
3330
3331 Ok(transformed)
3332}
3333
3334fn snap_down_to_origin(value: f64, origin: f64, step: f64) -> f64 {
3335 origin + ((value - origin) / step).floor() * step
3336}
3337
3338fn snap_up_to_origin(value: f64, origin: f64, step: f64) -> f64 {
3339 origin + ((value - origin) / step).ceil() * step
3340}
3341
3342fn wrap_lon_360(lon: f64) -> f64 {
3343 let mut v = lon % 360.0;
3344 if v < 0.0 {
3345 v += 360.0;
3346 }
3347 v
3348}
3349
3350fn minimal_wrapped_longitude_bounds(longitudes: &[f64]) -> Option<(f64, f64)> {
3351 if longitudes.is_empty() {
3352 return None;
3353 }
3354
3355 if longitudes.len() == 1 {
3356 let v = wrap_lon_360(longitudes[0]);
3357 return Some((v, v));
3358 }
3359
3360 let mut values: Vec<f64> = longitudes.iter().map(|v| wrap_lon_360(*v)).collect();
3361 values.sort_by(f64::total_cmp);
3362
3363 let n = values.len();
3364 let mut max_gap = f64::NEG_INFINITY;
3365 let mut max_gap_idx = 0usize;
3366 for i in 0..n {
3367 let next = if i + 1 < n {
3368 values[i + 1]
3369 } else {
3370 values[0] + 360.0
3371 };
3372 let gap = next - values[i];
3373 if gap > max_gap {
3374 max_gap = gap;
3375 max_gap_idx = i;
3376 }
3377 }
3378
3379 let start = values[(max_gap_idx + 1) % n];
3380 let mut end = values[max_gap_idx];
3381 if end < start {
3382 end += 360.0;
3383 }
3384
3385 Some((start, end))
3386}
3387
3388fn antimeridian_aware_longitude_bounds(
3389 longitudes: &[f64],
3390 policy: AntimeridianPolicy,
3391) -> Option<(f64, f64)> {
3392 if longitudes.is_empty() {
3393 return None;
3394 }
3395
3396 let mut lin_min = f64::INFINITY;
3397 let mut lin_max = f64::NEG_INFINITY;
3398 for lon in longitudes {
3399 if !lon.is_finite() {
3400 continue;
3401 }
3402 lin_min = lin_min.min(*lon);
3403 lin_max = lin_max.max(*lon);
3404 }
3405 if !lin_min.is_finite() || !lin_max.is_finite() {
3406 return None;
3407 }
3408
3409 if policy == AntimeridianPolicy::Linear {
3410 return Some((lin_min, lin_max));
3411 }
3412
3413 let Some((wrap_min, wrap_max)) = minimal_wrapped_longitude_bounds(longitudes) else {
3414 return Some((lin_min, lin_max));
3415 };
3416 if policy == AntimeridianPolicy::Wrap {
3417 return Some((wrap_min, wrap_max));
3418 }
3419
3420 let linear_width = lin_max - lin_min;
3421 let wrapped_width = wrap_max - wrap_min;
3422
3423 if wrapped_width + 1e-9 < linear_width {
3424 Some((wrap_min, wrap_max))
3425 } else {
3426 Some((lin_min, lin_max))
3427 }
3428}
3429
3430fn clamp_isize(v: isize, min_v: isize, max_v: isize) -> isize {
3431 v.max(min_v).min(max_v)
3432}
3433
3434fn point_in_polygon(x: f64, y: f64, polygon: &[(f64, f64)]) -> bool {
3435 if polygon.len() < 3 {
3436 return false;
3437 }
3438 let mut inside = false;
3439 let mut j = polygon.len() - 1;
3440 for i in 0..polygon.len() {
3441 let (xi, yi) = polygon[i];
3442 let (xj, yj) = polygon[j];
3443 let intersects = (yi > y) != (yj > y)
3444 && x < (xj - xi) * (y - yi) / ((yj - yi) + 1e-30) + xi;
3445 if intersects {
3446 inside = !inside;
3447 }
3448 j = i;
3449 }
3450 inside
3451}
3452
3453#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3454enum WindowStat {
3455 Mean,
3456 Min,
3457 Max,
3458 Mode,
3459 Median,
3460 StdDev,
3461}
3462
3463fn reduce_window_values(values: &[f64], stat: WindowStat) -> Option<f64> {
3464 if values.is_empty() {
3465 return None;
3466 }
3467
3468 match stat {
3469 WindowStat::Mean => Some(values.iter().sum::<f64>() / values.len() as f64),
3470 WindowStat::Min => values.iter().copied().reduce(f64::min),
3471 WindowStat::Max => values.iter().copied().reduce(f64::max),
3472 WindowStat::Mode => {
3473 let mut pairs: Vec<(f64, usize)> = Vec::new();
3474 for v in values {
3475 if let Some((_, count)) = pairs.iter_mut().find(|(u, _)| *u == *v) {
3476 *count += 1;
3477 } else {
3478 pairs.push((*v, 1));
3479 }
3480 }
3481 pairs.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.total_cmp(&b.0)));
3482 pairs.first().map(|(v, _)| *v)
3483 }
3484 WindowStat::Median => {
3485 let mut sorted = values.to_vec();
3486 sorted.sort_by(f64::total_cmp);
3487 let n = sorted.len();
3488 if n % 2 == 1 {
3489 Some(sorted[n / 2])
3490 } else {
3491 Some((sorted[n / 2 - 1] + sorted[n / 2]) / 2.0)
3492 }
3493 }
3494 WindowStat::StdDev => {
3495 let n = values.len() as f64;
3496 let mean = values.iter().sum::<f64>() / n;
3497 let variance = values
3498 .iter()
3499 .map(|v| {
3500 let d = *v - mean;
3501 d * d
3502 })
3503 .sum::<f64>()
3504 / n;
3505 Some(variance.max(0.0).sqrt())
3506 }
3507 }
3508}
3509
3510impl std::fmt::Display for Raster {
3511 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3512 write!(
3513 f,
3514 "Raster({}×{}×{}, cell={:.6}, x=[{:.4},{:.4}], y=[{:.4},{:.4}], type={}, nodata={})",
3515 self.bands,
3516 self.cols,
3517 self.rows,
3518 self.cell_size_x,
3519 self.x_min,
3520 self.x_max(),
3521 self.y_min,
3522 self.y_max(),
3523 self.data_type,
3524 self.nodata,
3525 )
3526 }
3527}
3528
3529#[cfg(test)]
3530mod tests {
3531 use super::*;
3532 use std::sync::{Arc, Mutex};
3533
3534 fn make_raster() -> Raster {
3535 let cfg = RasterConfig { cols: 4, rows: 3, cell_size: 10.0, nodata: -9999.0, ..Default::default() };
3536 let mut r = Raster::new(cfg);
3537 for row in 0..3 {
3538 for col in 0..4 {
3539 let _ = r.set(0, row, col, (row * 4 + col) as f64);
3540 }
3541 }
3542 r
3543 }
3544
3545 #[test]
3546 fn get_set() {
3547 let mut r = make_raster();
3548 assert_eq!(r.get(0, 0, 0), 0.0);
3549 assert_eq!(r.get(0, 2, 3), 11.0);
3550 r.set(0, 1, 1, -9999.0).unwrap();
3551 assert!(r.is_nodata(r.get(0, 1, 1))); assert_eq!(r.get_opt(0, 1, 1), None); }
3554
3555 #[test]
3556 fn statistics() {
3557 let r = make_raster();
3558 let s = r.statistics();
3559 assert_eq!(s.valid_count, 12);
3560 assert_eq!(s.min, 0.0);
3561 assert_eq!(s.max, 11.0);
3562 assert!((s.mean - 5.5).abs() < 1e-10);
3563 }
3564
3565 #[test]
3566 fn world_to_pixel() {
3567 let r = make_raster();
3568 assert_eq!(r.world_to_pixel(5.0, 25.0), Some((0, 0)));
3570 assert_eq!(r.world_to_pixel(35.0, 5.0), Some((3, 2)));
3571 assert_eq!(r.world_to_pixel(-1.0, 0.0), None);
3572 }
3573
3574 #[test]
3575 fn extent() {
3576 let r = make_raster();
3577 let e = r.extent();
3578 assert_eq!(e.x_min, 0.0);
3579 assert_eq!(e.y_min, 0.0);
3580 assert_eq!(e.x_max, 40.0);
3581 assert_eq!(e.y_max, 30.0);
3582 }
3583
3584 #[test]
3585 fn sample_extent_boundary_points_count_and_corners() {
3586 let e = Extent {
3587 x_min: 0.0,
3588 y_min: 0.0,
3589 x_max: 10.0,
3590 y_max: 5.0,
3591 };
3592 let pts = sample_extent_boundary_points(e, 8);
3593 assert_eq!(pts.len(), 32);
3594 assert!(pts.contains(&(0.0, 0.0)));
3595 assert!(pts.contains(&(0.0, 5.0)));
3596 assert!(pts.contains(&(10.0, 0.0)));
3597 assert!(pts.contains(&(10.0, 5.0)));
3598 }
3599
3600 #[test]
3601 fn sample_extent_boundary_points_minimum_sampling_when_zero_requested() {
3602 let e = Extent {
3603 x_min: -1.0,
3604 y_min: -2.0,
3605 x_max: 3.0,
3606 y_max: 4.0,
3607 };
3608 let pts = sample_extent_boundary_points(e, 0);
3609 assert_eq!(pts.len(), 4);
3610 assert!(pts.contains(&(-1.0, -2.0)));
3611 assert!(pts.contains(&(-1.0, 4.0)));
3612 assert!(pts.contains(&(3.0, -2.0)));
3613 assert!(pts.contains(&(3.0, 4.0)));
3614 }
3615
3616 #[test]
3617 fn minimal_wrapped_longitude_bounds_handles_antimeridian_cluster() {
3618 let lons = [179.0, -179.0, 178.0, -178.0];
3619 let (x0, x1) = minimal_wrapped_longitude_bounds(&lons).unwrap();
3620 assert!((x1 - x0) < 6.0);
3621 }
3622
3623 #[test]
3624 fn antimeridian_aware_longitude_bounds_prefers_wrapped_interval() {
3625 let lons = [179.5, -179.5, 179.0, -179.0];
3626 let (x0, x1) =
3627 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Auto).unwrap();
3628 assert!((x1 - x0) < 5.0);
3629 assert!(x0 >= 0.0);
3630 }
3631
3632 #[test]
3633 fn antimeridian_aware_longitude_bounds_keeps_linear_when_better() {
3634 let lons = [-20.0, -10.0, 0.0, 10.0];
3635 let (x0, x1) =
3636 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Auto).unwrap();
3637 assert!((x0 - (-20.0)).abs() < 1e-12);
3638 assert!((x1 - 10.0).abs() < 1e-12);
3639 }
3640
3641 #[test]
3642 fn antimeridian_policy_controls_wrapped_vs_linear_behavior() {
3643 let lons = [179.5, -179.5, 179.0, -179.0];
3644
3645 let (ax0, ax1) =
3646 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Auto).unwrap();
3647 let (lx0, lx1) =
3648 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Linear).unwrap();
3649 let (wx0, wx1) =
3650 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Wrap).unwrap();
3651
3652 assert!((ax1 - ax0) < (lx1 - lx0));
3653 assert!((wx1 - wx0) < (lx1 - lx0));
3654 assert!((wx1 - wx0 - (ax1 - ax0)).abs() < 1e-9);
3655 }
3656
3657 #[test]
3658 fn reproject_antimeridian_policy_linear_vs_wrap_changes_default_extent() {
3659 let cfg = RasterConfig {
3660 cols: 8,
3661 rows: 4,
3662 x_min: 170.0,
3663 y_min: -10.0,
3664 cell_size: 2.5,
3665 nodata: -9999.0,
3666 ..Default::default()
3667 };
3668 let mut r = Raster::new(cfg);
3669 r.crs = CrsInfo::from_epsg(4326);
3670 for row in 0..r.rows {
3671 for col in 0..r.cols {
3672 r.set(0, row as isize, col as isize, (row * r.cols + col) as f64)
3673 .unwrap();
3674 }
3675 }
3676
3677 let linear_opts = ReprojectOptions::new(4326, ResampleMethod::Nearest)
3678 .with_antimeridian_policy(AntimeridianPolicy::Linear);
3679 let wrap_opts = ReprojectOptions::new(4326, ResampleMethod::Nearest)
3680 .with_antimeridian_policy(AntimeridianPolicy::Wrap);
3681
3682 let linear = r.reproject_with_options(&linear_opts).unwrap();
3683 let wrap = r.reproject_with_options(&wrap_opts).unwrap();
3684
3685 let linear_width = linear.x_max() - linear.x_min;
3686 let wrap_width = wrap.x_max() - wrap.x_min;
3687
3688 assert!(linear_width.is_finite() && linear_width > 0.0);
3689 assert!(wrap_width.is_finite() && wrap_width > 0.0);
3690 assert!(wrap_width <= linear_width + 1e-9);
3691 }
3692
3693 #[test]
3694 fn reproject_antimeridian_policy_auto_matches_narrower_interval() {
3695 let cfg = RasterConfig {
3696 cols: 8,
3697 rows: 4,
3698 x_min: 170.0,
3699 y_min: -10.0,
3700 cell_size: 2.5,
3701 nodata: -9999.0,
3702 ..Default::default()
3703 };
3704 let mut r = Raster::new(cfg);
3705 r.crs = CrsInfo::from_epsg(4326);
3706 for row in 0..r.rows {
3707 for col in 0..r.cols {
3708 r.set(0, row as isize, col as isize, (row * r.cols + col) as f64)
3709 .unwrap();
3710 }
3711 }
3712
3713 let linear = r
3714 .reproject_with_options(
3715 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
3716 .with_antimeridian_policy(AntimeridianPolicy::Linear),
3717 )
3718 .unwrap();
3719 let wrap = r
3720 .reproject_with_options(
3721 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
3722 .with_antimeridian_policy(AntimeridianPolicy::Wrap),
3723 )
3724 .unwrap();
3725 let auto = r
3726 .reproject_with_options(
3727 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
3728 .with_antimeridian_policy(AntimeridianPolicy::Auto),
3729 )
3730 .unwrap();
3731
3732 let linear_width = linear.x_max() - linear.x_min;
3733 let wrap_width = wrap.x_max() - wrap.x_min;
3734 let auto_width = auto.x_max() - auto.x_min;
3735
3736 let expected = linear_width.min(wrap_width);
3737 assert!((auto_width - expected).abs() < 1e-9);
3738 }
3739
3740 #[test]
3741 fn reproject_to_epsg_requires_source_epsg() {
3742 let r = make_raster();
3743 let err = r.reproject_to_epsg_nearest(3857).unwrap_err();
3744 assert!(err
3745 .to_string()
3746 .contains("requires source CRS EPSG in raster.crs.epsg"));
3747 }
3748
3749 #[test]
3750 fn reproject_with_crs_allows_missing_source_epsg() {
3751 let cfg = RasterConfig {
3752 cols: 4,
3753 rows: 4,
3754 x_min: -1.0,
3755 y_min: -1.0,
3756 cell_size: 0.5,
3757 nodata: -9999.0,
3758 ..Default::default()
3759 };
3760 let mut r = Raster::new(cfg);
3761 for row in 0..4 {
3763 for col in 0..4 {
3764 r.set(0, row, col, (row * 4 + col) as f64).unwrap();
3765 }
3766 }
3767
3768 let src = Crs::from_epsg(4326).unwrap();
3769 let dst = Crs::from_epsg(3857).unwrap();
3770 let opts = ReprojectOptions::new(3857, ResampleMethod::Nearest);
3771
3772 let out = r.reproject_with_crs(&src, &dst, &opts).unwrap();
3773 assert_eq!(out.cols, 4);
3774 assert_eq!(out.rows, 4);
3775 assert_eq!(out.crs.epsg, Some(3857));
3776 assert!(out.statistics().valid_count > 0);
3777 }
3778
3779 #[test]
3780 fn reproject_with_options_and_progress_emits_live_updates() {
3781 let cfg = RasterConfig {
3782 cols: 6,
3783 rows: 5,
3784 x_min: -80.0,
3785 y_min: 40.0,
3786 cell_size: 0.1,
3787 nodata: -9999.0,
3788 ..Default::default()
3789 };
3790 let mut r = Raster::new(cfg);
3791 r.crs = CrsInfo::from_epsg(4326);
3792 for row in 0..r.rows {
3793 for col in 0..r.cols {
3794 r.set(0, row as isize, col as isize, (row * r.cols + col) as f64)
3795 .unwrap();
3796 }
3797 }
3798
3799 let progress_values: Arc<Mutex<Vec<f64>>> = Arc::new(Mutex::new(Vec::new()));
3800 let sink = Arc::clone(&progress_values);
3801
3802 let out = r
3803 .reproject_with_options_and_progress(
3804 &ReprojectOptions::new(3857, ResampleMethod::Nearest),
3805 move |pct| {
3806 sink.lock().unwrap().push(pct);
3807 },
3808 )
3809 .unwrap();
3810
3811 let values = progress_values.lock().unwrap();
3812 assert!(!values.is_empty());
3813 assert_eq!(values.len(), out.rows + 1);
3814 assert!(values.iter().all(|v| v.is_finite() && *v >= 0.0 && *v <= 1.0));
3815 assert!((values.last().copied().unwrap() - 1.0).abs() < 1e-12);
3816 }
3817
3818 #[test]
3819 fn reproject_to_epsg_identity_preserves_data() {
3820 let mut r = make_raster();
3821 r.crs = CrsInfo::from_epsg(4326);
3822
3823 let r2 = r.reproject_to_epsg(4326, ResampleMethod::Nearest).unwrap();
3824 assert_eq!(r2.cols, r.cols);
3825 assert_eq!(r2.rows, r.rows);
3826 assert_eq!(r2.bands, r.bands);
3827 assert_eq!(r2.crs.epsg, Some(4326));
3828 assert_eq!(r2.get(0, 0, 0), r.get(0, 0, 0));
3829 assert_eq!(r2.get(0, 2, 3), r.get(0, 2, 3));
3830 }
3831
3832 #[test]
3833 fn reproject_to_epsg_4326_to_3857_produces_valid_output() {
3834 let cfg = RasterConfig {
3835 cols: 4,
3836 rows: 4,
3837 x_min: -1.0,
3838 y_min: -1.0,
3839 cell_size: 0.5,
3840 nodata: -9999.0,
3841 ..Default::default()
3842 };
3843 let mut r = Raster::new(cfg);
3844 r.crs = CrsInfo::from_epsg(4326);
3845 for row in 0..4 {
3846 for col in 0..4 {
3847 r.set(0, row, col, (row * 4 + col) as f64).unwrap();
3848 }
3849 }
3850
3851 let out = r.reproject_to_epsg_nearest(3857).unwrap();
3852 assert_eq!(out.cols, 4);
3853 assert_eq!(out.rows, 4);
3854 assert_eq!(out.crs.epsg, Some(3857));
3855 assert!(out.x_min.is_finite());
3856 assert!(out.y_min.is_finite());
3857 assert!(out.cell_size_x.is_finite() && out.cell_size_x > 0.0);
3858 assert!(out.cell_size_y.is_finite() && out.cell_size_y > 0.0);
3859 let s = out.statistics();
3860 assert!(s.valid_count > 0);
3861 }
3862
3863 #[test]
3864 fn reproject_to_match_grid_honors_target_grid_definition() {
3865 let src_cfg = RasterConfig {
3866 cols: 6,
3867 rows: 4,
3868 x_min: -3.0,
3869 y_min: -2.0,
3870 cell_size: 1.0,
3871 nodata: -9999.0,
3872 ..Default::default()
3873 };
3874 let mut src = Raster::new(src_cfg);
3875 src.crs = CrsInfo::from_epsg(4326);
3876 for row in 0..src.rows {
3877 for col in 0..src.cols {
3878 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
3879 .unwrap();
3880 }
3881 }
3882
3883 let target_cfg = RasterConfig {
3884 cols: 12,
3885 rows: 10,
3886 x_min: -500_000.0,
3887 y_min: -400_000.0,
3888 cell_size: 1.0,
3889 cell_size_y: Some(1.0),
3890 nodata: -9999.0,
3891 ..Default::default()
3892 };
3893 let mut target = Raster::new(target_cfg);
3894 target.crs = CrsInfo::from_epsg(3857);
3895 target.cell_size_x = (500_000.0 - (-500_000.0)) / target.cols as f64;
3896 target.cell_size_y = (400_000.0 - (-400_000.0)) / target.rows as f64;
3897
3898 let out = src
3899 .reproject_to_match_grid(&target, ResampleMethod::Bilinear)
3900 .unwrap();
3901
3902 assert_eq!(out.cols, target.cols);
3903 assert_eq!(out.rows, target.rows);
3904 assert_eq!(out.crs.epsg, target.crs.epsg);
3905 assert!((out.x_min - target.x_min).abs() < 1e-9);
3906 assert!((out.y_min - target.y_min).abs() < 1e-9);
3907 assert!((out.x_max() - target.x_max()).abs() < 1e-6);
3908 assert!((out.y_max() - target.y_max()).abs() < 1e-6);
3909 assert!(out.cell_size_x.is_finite() && out.cell_size_x > 0.0);
3910 assert!(out.cell_size_y.is_finite() && out.cell_size_y > 0.0);
3911 }
3912
3913 #[test]
3914 fn reproject_to_match_resolution_honors_reference_cellsize_and_snap() {
3915 let src_cfg = RasterConfig {
3916 cols: 6,
3917 rows: 4,
3918 x_min: -3.0,
3919 y_min: -2.0,
3920 cell_size: 1.0,
3921 nodata: -9999.0,
3922 ..Default::default()
3923 };
3924 let mut src = Raster::new(src_cfg);
3925 src.crs = CrsInfo::from_epsg(4326);
3926 for row in 0..src.rows {
3927 for col in 0..src.cols {
3928 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
3929 .unwrap();
3930 }
3931 }
3932
3933 let mut reference = Raster::new(RasterConfig {
3934 cols: 4,
3935 rows: 3,
3936 x_min: 50_000.0,
3937 y_min: -75_000.0,
3938 cell_size: 100_000.0,
3939 cell_size_y: Some(80_000.0),
3940 nodata: -9999.0,
3941 ..Default::default()
3942 });
3943 reference.crs = CrsInfo::from_epsg(3857);
3944
3945 let out = src
3946 .reproject_to_match_resolution(&reference, ResampleMethod::Nearest)
3947 .unwrap();
3948
3949 assert_eq!(out.crs.epsg, Some(3857));
3950 assert!((out.cell_size_x - reference.cell_size_x).abs() < 1e-9);
3951 assert!((out.cell_size_y - reference.cell_size_y).abs() < 1e-9);
3952
3953 let kx = ((out.x_min - reference.x_min) / reference.cell_size_x).round();
3954 let ky = ((out.y_min - reference.y_min) / reference.cell_size_y).round();
3955 assert!((out.x_min - (reference.x_min + kx * reference.cell_size_x)).abs() < 1e-6);
3956 assert!((out.y_min - (reference.y_min + ky * reference.cell_size_y)).abs() < 1e-6);
3957 assert!(out.cols > 0);
3958 assert!(out.rows > 0);
3959 }
3960
3961 #[test]
3962 fn reproject_to_match_resolution_in_epsg_same_crs_matches_reference_settings() {
3963 let src_cfg = RasterConfig {
3964 cols: 6,
3965 rows: 4,
3966 x_min: -3.0,
3967 y_min: -2.0,
3968 cell_size: 1.0,
3969 nodata: -9999.0,
3970 ..Default::default()
3971 };
3972 let mut src = Raster::new(src_cfg);
3973 src.crs = CrsInfo::from_epsg(4326);
3974 for row in 0..src.rows {
3975 for col in 0..src.cols {
3976 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
3977 .unwrap();
3978 }
3979 }
3980
3981 let mut reference = Raster::new(RasterConfig {
3982 cols: 4,
3983 rows: 3,
3984 x_min: -10.0,
3985 y_min: -20.0,
3986 cell_size: 0.5,
3987 cell_size_y: Some(0.25),
3988 nodata: -9999.0,
3989 ..Default::default()
3990 });
3991 reference.crs = CrsInfo::from_epsg(4326);
3992
3993 let out = src
3994 .reproject_to_match_resolution_in_epsg(4326, &reference, ResampleMethod::Nearest)
3995 .unwrap();
3996
3997 assert_eq!(out.crs.epsg, Some(4326));
3998 assert!((out.cell_size_x - reference.cell_size_x).abs() < 1e-12);
3999 assert!((out.cell_size_y - reference.cell_size_y).abs() < 1e-12);
4000 }
4001
4002 #[test]
4003 fn reproject_to_match_resolution_in_epsg_cross_crs_converts_resolution() {
4004 let src_cfg = RasterConfig {
4005 cols: 6,
4006 rows: 4,
4007 x_min: -3.0,
4008 y_min: -2.0,
4009 cell_size: 1.0,
4010 nodata: -9999.0,
4011 ..Default::default()
4012 };
4013 let mut src = Raster::new(src_cfg);
4014 src.crs = CrsInfo::from_epsg(4326);
4015 for row in 0..src.rows {
4016 for col in 0..src.cols {
4017 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
4018 .unwrap();
4019 }
4020 }
4021
4022 let mut reference = Raster::new(RasterConfig {
4023 cols: 4,
4024 rows: 3,
4025 x_min: 0.0,
4026 y_min: 0.0,
4027 cell_size: 1.0,
4028 cell_size_y: Some(1.0),
4029 nodata: -9999.0,
4030 ..Default::default()
4031 });
4032 reference.crs = CrsInfo::from_epsg(4326);
4033
4034 let out = src
4035 .reproject_to_match_resolution_in_epsg(3857, &reference, ResampleMethod::Nearest)
4036 .unwrap();
4037
4038 assert_eq!(out.crs.epsg, Some(3857));
4039 assert!(out.cell_size_x.is_finite());
4040 assert!(out.cell_size_y.is_finite());
4041 assert!(out.cell_size_x > 0.0);
4042 assert!(out.cell_size_y > 0.0);
4043 }
4044
4045 #[test]
4046 fn reproject_to_epsg_bilinear_cubic_and_lanczos_produce_valid_output() {
4047 let cfg = RasterConfig {
4048 cols: 8,
4049 rows: 8,
4050 x_min: -2.0,
4051 y_min: -2.0,
4052 cell_size: 0.5,
4053 nodata: -9999.0,
4054 ..Default::default()
4055 };
4056 let mut r = Raster::new(cfg);
4057 r.crs = CrsInfo::from_epsg(4326);
4058 for row in 0..8 {
4059 for col in 0..8 {
4060 let val = (col as f64) * 10.0 + row as f64;
4061 r.set(0, row, col, val).unwrap();
4062 }
4063 }
4064
4065 let bilinear = r.reproject_to_epsg_bilinear(3857).unwrap();
4066 let cubic = r.reproject_to_epsg_cubic(3857).unwrap();
4067 let lanczos = r.reproject_to_epsg_lanczos(3857).unwrap();
4068
4069 assert_eq!(bilinear.cols, 8);
4070 assert_eq!(bilinear.rows, 8);
4071 assert_eq!(bilinear.crs.epsg, Some(3857));
4072 assert!(bilinear.statistics().valid_count > 0);
4073
4074 assert_eq!(cubic.cols, 8);
4075 assert_eq!(cubic.rows, 8);
4076 assert_eq!(cubic.crs.epsg, Some(3857));
4077 assert!(cubic.statistics().valid_count > 0);
4078
4079 assert_eq!(lanczos.cols, 8);
4080 assert_eq!(lanczos.rows, 8);
4081 assert_eq!(lanczos.crs.epsg, Some(3857));
4082 assert!(lanczos.statistics().valid_count > 0);
4083 }
4084
4085 #[test]
4086 fn reproject_with_options_honors_grid_controls() {
4087 let cfg = RasterConfig {
4088 cols: 6,
4089 rows: 4,
4090 x_min: -3.0,
4091 y_min: -2.0,
4092 cell_size: 1.0,
4093 nodata: -9999.0,
4094 ..Default::default()
4095 };
4096 let mut r = Raster::new(cfg);
4097 r.crs = CrsInfo::from_epsg(4326);
4098 for row in 0..4 {
4099 for col in 0..6 {
4100 r.set(0, row, col, (row * 6 + col) as f64).unwrap();
4101 }
4102 }
4103
4104 let opts = ReprojectOptions {
4105 dst_epsg: 3857,
4106 resample: ResampleMethod::Bilinear,
4107 cols: Some(12),
4108 rows: Some(10),
4109 extent: Some(Extent {
4110 x_min: -500_000.0,
4111 y_min: -400_000.0,
4112 x_max: 500_000.0,
4113 y_max: 400_000.0,
4114 }),
4115 x_res: None,
4116 y_res: None,
4117 snap_x: None,
4118 snap_y: None,
4119 nodata_policy: NodataPolicy::PartialKernel,
4120 antimeridian_policy: AntimeridianPolicy::Auto,
4121 grid_size_policy: GridSizePolicy::Expand,
4122 destination_footprint: DestinationFootprint::None,
4123 };
4124
4125 let out = r.reproject_with_options(&opts).unwrap();
4126 assert_eq!(out.cols, 12);
4127 assert_eq!(out.rows, 10);
4128 assert!((out.x_min - (-500_000.0)).abs() < 1e-9);
4129 assert!((out.y_min - (-400_000.0)).abs() < 1e-9);
4130 assert!((out.x_max() - 500_000.0).abs() < 1e-6);
4131 assert!((out.y_max() - 400_000.0).abs() < 1e-6);
4132 assert_eq!(out.crs.epsg, Some(3857));
4133 }
4134
4135 #[test]
4136 fn bilinear_partial_kernel_renormalizes_with_nodata() {
4137 let cfg = RasterConfig {
4138 cols: 2,
4139 rows: 2,
4140 x_min: 0.0,
4141 y_min: 0.0,
4142 cell_size: 1.0,
4143 nodata: -9999.0,
4144 ..Default::default()
4145 };
4146 let r = Raster::from_data(cfg, vec![1.0, -9999.0, 3.0, 5.0]).unwrap();
4148
4149 let v = r.sample_bilinear_partial_pixel(0, 0.5, 0.5).unwrap();
4150 assert!((v - 3.0).abs() < 1e-9);
4152 }
4153
4154 #[test]
4155 fn bilinear_partial_kernel_handles_edges() {
4156 let cfg = RasterConfig {
4157 cols: 2,
4158 rows: 2,
4159 x_min: 0.0,
4160 y_min: 0.0,
4161 cell_size: 1.0,
4162 nodata: -9999.0,
4163 ..Default::default()
4164 };
4165 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
4166 let v = r.sample_bilinear_partial_pixel(0, -0.2, 0.3).unwrap();
4167 assert!(v.is_finite());
4168 }
4169
4170 #[test]
4171 fn cubic_partial_kernel_handles_edges_and_nodata() {
4172 let cfg = RasterConfig {
4173 cols: 4,
4174 rows: 4,
4175 x_min: 0.0,
4176 y_min: 0.0,
4177 cell_size: 1.0,
4178 nodata: -9999.0,
4179 ..Default::default()
4180 };
4181 let mut data = Vec::new();
4182 for row in 0..4 {
4183 for col in 0..4 {
4184 data.push((row * 4 + col) as f64);
4185 }
4186 }
4187 data[5] = -9999.0; let r = Raster::from_data(cfg, data).unwrap();
4189
4190 let edge_v = r.sample_cubic_partial_pixel(0, -0.25, 0.2).unwrap();
4191 assert!(edge_v.is_finite());
4192
4193 let nodata_v = r.sample_cubic_partial_pixel(0, 1.25, 1.25).unwrap();
4194 assert!(nodata_v.is_finite());
4195 }
4196
4197 #[test]
4198 fn strict_policy_rejects_incomplete_bilinear_kernel() {
4199 let cfg = RasterConfig {
4200 cols: 2,
4201 rows: 2,
4202 x_min: 0.0,
4203 y_min: 0.0,
4204 cell_size: 1.0,
4205 nodata: -9999.0,
4206 ..Default::default()
4207 };
4208 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
4209
4210 assert!(r.sample_bilinear_strict_pixel(0, -0.2, 0.3).is_none());
4211 assert!(r.sample_bilinear_partial_pixel(0, -0.2, 0.3).is_some());
4212 }
4213
4214 #[test]
4215 fn bilinear_strict_simd_matches_expected_for_f64_and_f32_storage() {
4216 let cfg_f64 = RasterConfig {
4217 cols: 2,
4218 rows: 2,
4219 x_min: 0.0,
4220 y_min: 0.0,
4221 cell_size: 1.0,
4222 nodata: -9999.0,
4223 data_type: DataType::F64,
4224 ..Default::default()
4225 };
4226 let cfg_f32 = RasterConfig {
4227 data_type: DataType::F32,
4228 ..cfg_f64.clone()
4229 };
4230 let data = vec![1.0, 2.0, 3.0, 5.0];
4231 let r64 = Raster::from_data(cfg_f64, data.clone()).unwrap();
4232 let r32 = Raster::from_data(cfg_f32, data).unwrap();
4233
4234 let expected = 2.375;
4235 let v64 = r64.sample_bilinear_strict_pixel(0, 0.25, 0.5).unwrap();
4236 let v32 = r32.sample_bilinear_strict_pixel(0, 0.25, 0.5).unwrap();
4237 assert!((v64 - expected).abs() < 1e-9);
4238 assert!((v32 - expected).abs() < 1e-6);
4239 }
4240
4241 #[test]
4242 fn lanczos_strict_simd_matches_between_f64_and_f32_storage() {
4243 let cfg_f64 = RasterConfig {
4244 cols: 16,
4245 rows: 16,
4246 x_min: 0.0,
4247 y_min: 0.0,
4248 cell_size: 1.0,
4249 nodata: -9999.0,
4250 data_type: DataType::F64,
4251 ..Default::default()
4252 };
4253 let cfg_f32 = RasterConfig {
4254 data_type: DataType::F32,
4255 ..cfg_f64.clone()
4256 };
4257
4258 let mut data = Vec::with_capacity(16 * 16);
4259 for row in 0..16 {
4260 for col in 0..16 {
4261 data.push(((row * 16 + col) as f64).sin() * 100.0 + (row as f64 * 0.5));
4262 }
4263 }
4264
4265 let r64 = Raster::from_data(cfg_f64, data.clone()).unwrap();
4266 let r32 = Raster::from_data(cfg_f32, data).unwrap();
4267
4268 let v64 = r64.sample_lanczos_strict_pixel(0, 7.25, 8.5).unwrap();
4269 let v32 = r32.sample_lanczos_strict_pixel(0, 7.25, 8.5).unwrap();
4270 assert!((v64 - v32).abs() < 1e-4);
4271 }
4272
4273 #[test]
4274 fn fill_policy_falls_back_to_nearest_for_bilinear() {
4275 let cfg = RasterConfig {
4276 cols: 2,
4277 rows: 2,
4278 x_min: 0.0,
4279 y_min: 0.0,
4280 cell_size: 1.0,
4281 nodata: -9999.0,
4282 ..Default::default()
4283 };
4284 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
4285
4286 let v = r.sample_world(0, 0.1, 1.1, ResampleMethod::Bilinear, NodataPolicy::Fill);
4287 assert_eq!(v, Some(10.0));
4288 }
4289
4290 #[test]
4291 fn fill_policy_falls_back_to_nearest_for_lanczos() {
4292 let cfg = RasterConfig {
4293 cols: 2,
4294 rows: 2,
4295 x_min: 0.0,
4296 y_min: 0.0,
4297 cell_size: 1.0,
4298 nodata: -9999.0,
4299 ..Default::default()
4300 };
4301 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
4302
4303 let v = r.sample_world(0, 0.1, 1.1, ResampleMethod::Lanczos, NodataPolicy::Fill);
4304 assert_eq!(v, Some(10.0));
4305 }
4306
4307 #[test]
4308 fn reproject_options_default_to_partial_kernel_policy() {
4309 let opts = ReprojectOptions::new(3857, ResampleMethod::Cubic);
4310 assert_eq!(opts.nodata_policy, NodataPolicy::PartialKernel);
4311 assert_eq!(opts.x_res, None);
4312 assert_eq!(opts.y_res, None);
4313 assert_eq!(opts.snap_x, None);
4314 assert_eq!(opts.snap_y, None);
4315 assert_eq!(opts.antimeridian_policy, AntimeridianPolicy::Auto);
4316 assert_eq!(opts.grid_size_policy, GridSizePolicy::Expand);
4317 assert_eq!(opts.destination_footprint, DestinationFootprint::None);
4318
4319 let strict = opts.with_nodata_policy(NodataPolicy::Strict);
4320 assert_eq!(strict.nodata_policy, NodataPolicy::Strict);
4321
4322 let sized = strict.with_size(321, 123);
4323 assert_eq!(sized.cols, Some(321));
4324 assert_eq!(sized.rows, Some(123));
4325
4326 let e = Extent {
4327 x_min: -10.0,
4328 y_min: -5.0,
4329 x_max: 10.0,
4330 y_max: 5.0,
4331 };
4332 let ext = sized.with_extent(e);
4333 assert_eq!(ext.extent, Some(e));
4334
4335 let res = ext.with_resolution(30.0, 40.0);
4336 assert_eq!(res.x_res, Some(30.0));
4337 assert_eq!(res.y_res, Some(40.0));
4338
4339 let square = res.with_square_resolution(25.0);
4340 assert_eq!(square.x_res, Some(25.0));
4341 assert_eq!(square.y_res, Some(25.0));
4342
4343 let snapped = square.with_snap_origin(0.0, 0.0);
4344 assert_eq!(snapped.snap_x, Some(0.0));
4345 assert_eq!(snapped.snap_y, Some(0.0));
4346
4347 let linear = snapped.with_antimeridian_policy(AntimeridianPolicy::Linear);
4348 assert_eq!(linear.antimeridian_policy, AntimeridianPolicy::Linear);
4349
4350 let fit_inside = linear.with_grid_size_policy(GridSizePolicy::FitInside);
4351 assert_eq!(fit_inside.grid_size_policy, GridSizePolicy::FitInside);
4352
4353 let masked = fit_inside.with_destination_footprint(DestinationFootprint::SourceBoundary);
4354 assert_eq!(masked.destination_footprint, DestinationFootprint::SourceBoundary);
4355 }
4356
4357 #[test]
4358 fn sample_extent_boundary_ring_count_and_corners() {
4359 let e = Extent {
4360 x_min: 0.0,
4361 y_min: 0.0,
4362 x_max: 10.0,
4363 y_max: 5.0,
4364 };
4365 let ring = sample_extent_boundary_ring(e, 8);
4366 assert_eq!(ring.len(), 32);
4367 assert!(ring.contains(&(0.0, 0.0)));
4368 assert!(ring.contains(&(10.0, 0.0)));
4369 assert!(ring.contains(&(10.0, 5.0)));
4370 assert!(ring.contains(&(0.0, 5.0)));
4371 }
4372
4373 #[test]
4374 fn point_in_polygon_identifies_inside_and_outside_points() {
4375 let poly = vec![(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)];
4376 assert!(point_in_polygon(2.0, 2.0, &poly));
4377 assert!(!point_in_polygon(5.0, 2.0, &poly));
4378 }
4379
4380 #[test]
4381 fn thematic_3x3_resamplers_return_expected_statistics() {
4382 let cfg = RasterConfig {
4383 cols: 3,
4384 rows: 3,
4385 x_min: 0.0,
4386 y_min: 0.0,
4387 cell_size: 1.0,
4388 nodata: -9999.0,
4389 ..Default::default()
4390 };
4391 let data = vec![
4392 1.0, 2.0, 2.0,
4393 3.0, 4.0, 4.0,
4394 5.0, 6.0, 6.0,
4395 ];
4396 let expected_mean = data.iter().sum::<f64>() / data.len() as f64;
4397 let expected_stddev = (data
4398 .iter()
4399 .map(|v| {
4400 let d = *v - expected_mean;
4401 d * d
4402 })
4403 .sum::<f64>()
4404 / data.len() as f64)
4405 .sqrt();
4406 let r = Raster::from_data(cfg, data).unwrap();
4407
4408 let x = r.col_center_x(1);
4409 let y = r.row_center_y(1);
4410
4411 let avg = r
4412 .sample_world(0, x, y, ResampleMethod::Average, NodataPolicy::Strict)
4413 .unwrap();
4414 let min = r
4415 .sample_world(0, x, y, ResampleMethod::Min, NodataPolicy::Strict)
4416 .unwrap();
4417 let max = r
4418 .sample_world(0, x, y, ResampleMethod::Max, NodataPolicy::Strict)
4419 .unwrap();
4420 let mode = r
4421 .sample_world(0, x, y, ResampleMethod::Mode, NodataPolicy::Strict)
4422 .unwrap();
4423 let median = r
4424 .sample_world(0, x, y, ResampleMethod::Median, NodataPolicy::Strict)
4425 .unwrap();
4426 let stddev = r
4427 .sample_world(0, x, y, ResampleMethod::StdDev, NodataPolicy::Strict)
4428 .unwrap();
4429
4430 assert!((avg - (33.0 / 9.0)).abs() < 1e-9);
4431 assert_eq!(min, 1.0);
4432 assert_eq!(max, 6.0);
4433 assert_eq!(mode, 2.0);
4434 assert_eq!(median, 4.0);
4435 assert!((stddev - expected_stddev).abs() < 1e-9);
4436 }
4437
4438 #[test]
4439 fn grid_size_policy_fit_inside_reduces_resolution_derived_size() {
4440 let mut r = make_raster();
4441 r.crs = CrsInfo::from_epsg(4326);
4442
4443 let extent = Extent {
4444 x_min: -500_000.0,
4445 y_min: -400_000.0,
4446 x_max: 500_000.0,
4447 y_max: 400_000.0,
4448 };
4449
4450 let expand = r
4451 .reproject_with_options(
4452 &ReprojectOptions::new(3857, ResampleMethod::Nearest)
4453 .with_extent(extent)
4454 .with_resolution(300_000.0, 300_000.0)
4455 .with_grid_size_policy(GridSizePolicy::Expand),
4456 )
4457 .unwrap();
4458
4459 let fit = r
4460 .reproject_with_options(
4461 &ReprojectOptions::new(3857, ResampleMethod::Nearest)
4462 .with_extent(extent)
4463 .with_resolution(300_000.0, 300_000.0)
4464 .with_grid_size_policy(GridSizePolicy::FitInside),
4465 )
4466 .unwrap();
4467
4468 assert!(fit.cols <= expand.cols);
4469 assert!(fit.rows <= expand.rows);
4470 }
4471
4472 #[test]
4473 fn destination_footprint_masks_cells_outside_source_boundary() {
4474 let cfg = RasterConfig {
4475 cols: 4,
4476 rows: 4,
4477 x_min: 0.0,
4478 y_min: 0.0,
4479 cell_size: 1.0,
4480 nodata: -9999.0,
4481 ..Default::default()
4482 };
4483 let mut r = Raster::new(cfg);
4484 r.crs = CrsInfo::from_epsg(4326);
4485 for row in 0..4 {
4486 for col in 0..4 {
4487 r.set(0, row, col, 1.0).unwrap();
4488 }
4489 }
4490
4491 let out = r
4492 .reproject_with_options(
4493 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
4494 .with_extent(Extent {
4495 x_min: -1.0,
4496 y_min: -1.0,
4497 x_max: 5.0,
4498 y_max: 5.0,
4499 })
4500 .with_size(6, 6)
4501 .with_destination_footprint(DestinationFootprint::SourceBoundary),
4502 )
4503 .unwrap();
4504
4505 assert!(out.is_nodata(out.get(0, 0, 0)));
4506 assert!(!out.is_nodata(out.get(0, 2, 2)));
4507 }
4508
4509 #[test]
4510 fn reproject_with_options_honors_snap_origin_with_resolution() {
4511 let cfg = RasterConfig {
4512 cols: 6,
4513 rows: 4,
4514 x_min: -3.0,
4515 y_min: -2.0,
4516 cell_size: 1.0,
4517 nodata: -9999.0,
4518 ..Default::default()
4519 };
4520 let mut r = Raster::new(cfg);
4521 r.crs = CrsInfo::from_epsg(4326);
4522 for row in 0..4 {
4523 for col in 0..6 {
4524 r.set(0, row, col, (row * 6 + col) as f64).unwrap();
4525 }
4526 }
4527
4528 let out_extent = Extent {
4529 x_min: -510_000.0,
4530 y_min: -390_000.0,
4531 x_max: 490_000.0,
4532 y_max: 410_000.0,
4533 };
4534 let opts = ReprojectOptions::new(3857, ResampleMethod::Bilinear)
4535 .with_extent(out_extent)
4536 .with_resolution(200_000.0, 160_000.0)
4537 .with_snap_origin(0.0, 0.0);
4538
4539 let out = r.reproject_with_options(&opts).unwrap();
4540 assert!((out.x_min - (-600_000.0)).abs() < 1e-6);
4541 assert!((out.y_min - (-480_000.0)).abs() < 1e-6);
4542 assert!((out.cell_size_x - 200_000.0).abs() < 1e-6);
4543 assert!((out.cell_size_y - 160_000.0).abs() < 1e-6);
4544 }
4545
4546 #[test]
4547 fn reproject_with_options_honors_resolution_controls() {
4548 let cfg = RasterConfig {
4549 cols: 6,
4550 rows: 4,
4551 x_min: -3.0,
4552 y_min: -2.0,
4553 cell_size: 1.0,
4554 nodata: -9999.0,
4555 ..Default::default()
4556 };
4557 let mut r = Raster::new(cfg);
4558 r.crs = CrsInfo::from_epsg(4326);
4559 for row in 0..4 {
4560 for col in 0..6 {
4561 r.set(0, row, col, (row * 6 + col) as f64).unwrap();
4562 }
4563 }
4564
4565 let out_extent = Extent {
4566 x_min: -500_000.0,
4567 y_min: -400_000.0,
4568 x_max: 500_000.0,
4569 y_max: 400_000.0,
4570 };
4571 let opts = ReprojectOptions::new(3857, ResampleMethod::Bilinear)
4572 .with_extent(out_extent)
4573 .with_resolution(200_000.0, 160_000.0);
4574
4575 let out = r.reproject_with_options(&opts).unwrap();
4576 assert_eq!(out.cols, 5);
4577 assert_eq!(out.rows, 5);
4578 assert!((out.cell_size_x - 200_000.0).abs() < 1e-6);
4579 assert!((out.cell_size_y - 160_000.0).abs() < 1e-6);
4580 }
4581
4582 #[test]
4583 fn reproject_with_options_rejects_invalid_resolution_controls() {
4584 let mut r = make_raster();
4585 r.crs = CrsInfo::from_epsg(4326);
4586 let opts = ReprojectOptions::new(3857, ResampleMethod::Nearest)
4587 .with_square_resolution(0.0);
4588 let err = r.reproject_with_options(&opts).unwrap_err();
4589 assert!(err
4590 .to_string()
4591 .contains("invalid reprojection resolution"));
4592 }
4593
4594 #[test]
4595 fn band_helpers() {
4596 let cfg = RasterConfig {
4597 cols: 3,
4598 rows: 2,
4599 bands: 2,
4600 nodata: -9999.0,
4601 ..Default::default()
4602 };
4603 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, -9999.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0];
4604 let mut r = Raster::from_data(cfg, data).unwrap();
4605
4606 let b0 = r.band_slice(0);
4607 assert_eq!(b0.len(), 6);
4608 assert_eq!(b0[0], 1.0);
4609 assert_eq!(b0[5], -9999.0);
4610
4611 let s0 = r.statistics_band(0).unwrap();
4612 assert_eq!(s0.valid_count, 5);
4613 assert_eq!(s0.nodata_count, 1);
4614 assert_eq!(s0.min, 1.0);
4615 assert_eq!(s0.max, 5.0);
4616
4617 r.set_band_slice(1, &[7.0, 8.0, 9.0, 10.0, 11.0, 12.0]).unwrap();
4618 assert_eq!(r.get_raw(1, 0, 0), Some(7.0));
4619 assert_eq!(r.get_raw(1, 1, 2), Some(12.0));
4620 }
4621
4622 #[test]
4623 fn band_transform_helpers() {
4624 let cfg = RasterConfig {
4625 cols: 2,
4626 rows: 2,
4627 bands: 2,
4628 nodata: -9999.0,
4629 ..Default::default()
4630 };
4631 let data = vec![1.0, 2.0, 3.0, -9999.0, 10.0, 20.0, 30.0, 40.0];
4632 let mut r = Raster::from_data(cfg, data).unwrap();
4633
4634 r.map_valid_band(0, |v| v * 2.0).unwrap();
4635 assert_eq!(r.get_raw(0, 0, 0), Some(2.0));
4636 assert_eq!(r.get_raw(0, 0, 1), Some(4.0));
4637 assert!(r.is_nodata(r.get(0, 1, 1))); assert_eq!(r.get_raw(1, 0, 0), Some(10.0)); r.replace_band(1, 20.0, 99.0).unwrap();
4641 assert_eq!(r.get_raw(1, 0, 1), Some(99.0));
4642 assert_eq!(r.get_raw(1, 0, 0), Some(10.0));
4643 assert_eq!(r.get_raw(0, 0, 1), Some(4.0));
4644 }
4645
4646 #[test]
4647 fn band_iterators() {
4648 let cfg = RasterConfig {
4649 cols: 3,
4650 rows: 2,
4651 bands: 2,
4652 nodata: -9999.0,
4653 ..Default::default()
4654 };
4655 let data = vec![1.0, 2.0, -9999.0, 4.0, 5.0, 6.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0];
4656 let r = Raster::from_data(cfg, data).unwrap();
4657
4658 let valid_b0: Vec<_> = r.iter_valid_band(0).unwrap().collect();
4659 assert_eq!(valid_b0.len(), 5);
4660 assert_eq!(valid_b0[0], (0, 0, 1.0));
4661 assert_eq!(valid_b0[4], (1, 2, 6.0));
4662
4663 let rows_b1: Vec<Vec<f64>> = r.iter_band_rows(1).unwrap().collect();
4664 assert_eq!(rows_b1.len(), 2);
4665 assert_eq!(rows_b1[0], vec![10.0, 20.0, 30.0]);
4666 assert_eq!(rows_b1[1], vec![40.0, 50.0, 60.0]);
4667 }
4668
4669 #[test]
4670 fn mutable_band_rows_native() {
4671 let cfg = RasterConfig {
4672 cols: 3,
4673 rows: 2,
4674 bands: 1,
4675 data_type: DataType::F32,
4676 nodata: -9999.0,
4677 ..Default::default()
4678 };
4679 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
4680 let mut r = Raster::from_data(cfg, data).unwrap();
4681
4682 r.for_each_band_row_mut(0, |row, row_mut| {
4683 if let RasterRowMut::F32(slice) = row_mut {
4684 for v in slice.iter_mut() {
4685 *v += row as f32;
4686 }
4687 }
4688 })
4689 .unwrap();
4690
4691 assert_eq!(r.get_raw(0, 0, 0), Some(1.0));
4692 assert_eq!(r.get_raw(0, 0, 2), Some(3.0));
4693 assert_eq!(r.get_raw(0, 1, 0), Some(5.0));
4694 assert_eq!(r.get_raw(0, 1, 2), Some(7.0));
4695 }
4696
4697 #[test]
4698 fn immutable_band_rows_native() {
4699 let cfg = RasterConfig {
4700 cols: 3,
4701 rows: 2,
4702 bands: 1,
4703 data_type: DataType::U16,
4704 nodata: 0.0,
4705 ..Default::default()
4706 };
4707 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
4708 let r = Raster::from_data(cfg, data).unwrap();
4709
4710 let mut sums = Vec::new();
4711 r.for_each_band_row(0, |_row, row_ref| {
4712 if let RasterRowRef::U16(slice) = row_ref {
4713 sums.push(slice.iter().map(|v| *v as u64).sum::<u64>());
4714 }
4715 })
4716 .unwrap();
4717
4718 assert_eq!(sums, vec![6, 15]);
4719 }
4720}