1use std::path::Path;
4use std::sync::atomic::{AtomicUsize, Ordering};
5use rayon::prelude::*;
6use wbprojection::{from_proj_string, 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)]
111pub struct BandView {
112 data: Vec<f64>,
113 pub rows: isize,
115 pub cols: isize,
117 pub nodata: f64,
119}
120
121impl BandView {
122 #[inline]
126 pub fn get(&self, row: isize, col: isize) -> f64 {
127 if row < 0 || col < 0 || row >= self.rows || col >= self.cols {
128 return self.nodata;
129 }
130 self.data[row as usize * self.cols as usize + col as usize]
131 }
132
133 #[inline]
135 pub fn is_nodata(&self, v: f64) -> bool {
136 if self.nodata.is_nan() { v.is_nan() } else { v == self.nodata }
137 }
138
139 #[inline]
142 pub fn as_slice(&self) -> &[f64] { &self.data }
143}
144
145unsafe impl Send for BandView {}
147unsafe impl Sync for BandView {}
148
149#[derive(Debug, Clone)]
151pub enum RasterData {
152 U8(Vec<u8>),
154 I8(Vec<i8>),
156 U16(Vec<u16>),
158 I16(Vec<i16>),
160 U32(Vec<u32>),
162 I32(Vec<i32>),
164 U64(Vec<u64>),
166 I64(Vec<i64>),
168 F32(Vec<f32>),
170 F64(Vec<f64>),
172}
173
174pub enum RasterRowMut<'a> {
176 U8(&'a mut [u8]),
178 I8(&'a mut [i8]),
180 U16(&'a mut [u16]),
182 I16(&'a mut [i16]),
184 U32(&'a mut [u32]),
186 I32(&'a mut [i32]),
188 U64(&'a mut [u64]),
190 I64(&'a mut [i64]),
192 F32(&'a mut [f32]),
194 F64(&'a mut [f64]),
196}
197
198pub enum RasterRowRef<'a> {
200 U8(&'a [u8]),
202 I8(&'a [i8]),
204 U16(&'a [u16]),
206 I16(&'a [i16]),
208 U32(&'a [u32]),
210 I32(&'a [i32]),
212 U64(&'a [u64]),
214 I64(&'a [i64]),
216 F32(&'a [f32]),
218 F64(&'a [f64]),
220}
221
222impl RasterData {
223 pub fn new_filled(data_type: DataType, len: usize, value: f64) -> Self {
226 match data_type {
227 DataType::U8 => Self::U8(vec![value as u8; len]),
228 DataType::I8 => Self::I8(vec![value as i8; len]),
229 DataType::U16 => Self::U16(vec![value as u16; len]),
230 DataType::I16 => Self::I16(vec![value as i16; len]),
231 DataType::U32 => Self::U32(vec![value as u32; len]),
232 DataType::I32 => Self::I32(vec![value as i32; len]),
233 DataType::U64 => Self::U64(vec![value as u64; len]),
234 DataType::I64 => Self::I64(vec![value as i64; len]),
235 DataType::F32 => Self::F32(vec![value as f32; len]),
236 DataType::F64 => Self::F64(vec![value; len]),
237 }
238 }
239
240 pub fn new_uninit(data_type: DataType, len: usize) -> Self {
247 fn uninit_vec<T>(len: usize) -> Vec<T> {
248 let mut v = Vec::with_capacity(len);
249 unsafe { v.set_len(len) };
252 v
253 }
254 match data_type {
255 DataType::U8 => Self::U8(uninit_vec(len)),
256 DataType::I8 => Self::I8(uninit_vec(len)),
257 DataType::U16 => Self::U16(uninit_vec(len)),
258 DataType::I16 => Self::I16(uninit_vec(len)),
259 DataType::U32 => Self::U32(uninit_vec(len)),
260 DataType::I32 => Self::I32(uninit_vec(len)),
261 DataType::U64 => Self::U64(uninit_vec(len)),
262 DataType::I64 => Self::I64(uninit_vec(len)),
263 DataType::F32 => Self::F32(uninit_vec(len)),
264 DataType::F64 => Self::F64(uninit_vec(len)),
265 }
266 }
267
268 pub fn from_f64_vec(data_type: DataType, data: Vec<f64>) -> Self {
270 match data_type {
271 DataType::U8 => Self::U8(data.into_iter().map(|v| v as u8).collect()),
272 DataType::I8 => Self::I8(data.into_iter().map(|v| v as i8).collect()),
273 DataType::U16 => Self::U16(data.into_iter().map(|v| v as u16).collect()),
274 DataType::I16 => Self::I16(data.into_iter().map(|v| v as i16).collect()),
275 DataType::U32 => Self::U32(data.into_iter().map(|v| v as u32).collect()),
276 DataType::I32 => Self::I32(data.into_iter().map(|v| v as i32).collect()),
277 DataType::U64 => Self::U64(data.into_iter().map(|v| v as u64).collect()),
278 DataType::I64 => Self::I64(data.into_iter().map(|v| v as i64).collect()),
279 DataType::F32 => Self::F32(data.into_iter().map(|v| v as f32).collect()),
280 DataType::F64 => Self::F64(data),
281 }
282 }
283
284 pub fn len(&self) -> usize {
286 match self {
287 Self::U8(v) => v.len(),
288 Self::I8(v) => v.len(),
289 Self::U16(v) => v.len(),
290 Self::I16(v) => v.len(),
291 Self::U32(v) => v.len(),
292 Self::I32(v) => v.len(),
293 Self::U64(v) => v.len(),
294 Self::I64(v) => v.len(),
295 Self::F32(v) => v.len(),
296 Self::F64(v) => v.len(),
297 }
298 }
299
300 pub fn is_empty(&self) -> bool {
302 self.len() == 0
303 }
304
305 pub fn data_type(&self) -> DataType {
307 match self {
308 Self::U8(_) => DataType::U8,
309 Self::I8(_) => DataType::I8,
310 Self::U16(_) => DataType::U16,
311 Self::I16(_) => DataType::I16,
312 Self::U32(_) => DataType::U32,
313 Self::I32(_) => DataType::I32,
314 Self::U64(_) => DataType::U64,
315 Self::I64(_) => DataType::I64,
316 Self::F32(_) => DataType::F32,
317 Self::F64(_) => DataType::F64,
318 }
319 }
320
321 pub fn get_f64(&self, idx: usize) -> f64 {
323 match self {
324 Self::U8(v) => v[idx] as f64,
325 Self::I8(v) => v[idx] as f64,
326 Self::U16(v) => v[idx] as f64,
327 Self::I16(v) => v[idx] as f64,
328 Self::U32(v) => v[idx] as f64,
329 Self::I32(v) => v[idx] as f64,
330 Self::U64(v) => v[idx] as f64,
331 Self::I64(v) => v[idx] as f64,
332 Self::F32(v) => v[idx] as f64,
333 Self::F64(v) => v[idx],
334 }
335 }
336
337 pub fn set_f64(&mut self, idx: usize, value: f64) {
339 match self {
340 Self::U8(v) => v[idx] = value as u8,
341 Self::I8(v) => v[idx] = value as i8,
342 Self::U16(v) => v[idx] = value as u16,
343 Self::I16(v) => v[idx] = value as i16,
344 Self::U32(v) => v[idx] = value as u32,
345 Self::I32(v) => v[idx] = value as i32,
346 Self::U64(v) => v[idx] = value as u64,
347 Self::I64(v) => v[idx] = value as i64,
348 Self::F32(v) => v[idx] = value as f32,
349 Self::F64(v) => v[idx] = value,
350 }
351 }
352
353 pub fn iter_f64(&self) -> Box<dyn Iterator<Item = f64> + '_> {
355 match self {
356 Self::U8(v) => Box::new(v.iter().copied().map(|x| x as f64)),
357 Self::I8(v) => Box::new(v.iter().copied().map(|x| x as f64)),
358 Self::U16(v) => Box::new(v.iter().copied().map(|x| x as f64)),
359 Self::I16(v) => Box::new(v.iter().copied().map(|x| x as f64)),
360 Self::U32(v) => Box::new(v.iter().copied().map(|x| x as f64)),
361 Self::I32(v) => Box::new(v.iter().copied().map(|x| x as f64)),
362 Self::U64(v) => Box::new(v.iter().copied().map(|x| x as f64)),
363 Self::I64(v) => Box::new(v.iter().copied().map(|x| x as f64)),
364 Self::F32(v) => Box::new(v.iter().copied().map(|x| x as f64)),
365 Self::F64(v) => Box::new(v.iter().copied()),
366 }
367 }
368
369 pub fn to_f64_vec(&self) -> Vec<f64> {
371 self.iter_f64().collect()
372 }
373
374 pub fn as_u8_slice(&self) -> Option<&[u8]> {
376 match self {
377 Self::U8(v) => Some(v.as_slice()),
378 _ => None,
379 }
380 }
381
382 pub fn as_u8_slice_mut(&mut self) -> Option<&mut [u8]> {
384 match self {
385 Self::U8(v) => Some(v.as_mut_slice()),
386 _ => None,
387 }
388 }
389
390 pub fn as_i8_slice(&self) -> Option<&[i8]> {
392 match self {
393 Self::I8(v) => Some(v.as_slice()),
394 _ => None,
395 }
396 }
397
398 pub fn as_i8_slice_mut(&mut self) -> Option<&mut [i8]> {
400 match self {
401 Self::I8(v) => Some(v.as_mut_slice()),
402 _ => None,
403 }
404 }
405
406 pub fn as_u16_slice(&self) -> Option<&[u16]> {
408 match self {
409 Self::U16(v) => Some(v.as_slice()),
410 _ => None,
411 }
412 }
413
414 pub fn as_u16_slice_mut(&mut self) -> Option<&mut [u16]> {
416 match self {
417 Self::U16(v) => Some(v.as_mut_slice()),
418 _ => None,
419 }
420 }
421
422 pub fn as_i16_slice(&self) -> Option<&[i16]> {
424 match self {
425 Self::I16(v) => Some(v.as_slice()),
426 _ => None,
427 }
428 }
429
430 pub fn as_i16_slice_mut(&mut self) -> Option<&mut [i16]> {
432 match self {
433 Self::I16(v) => Some(v.as_mut_slice()),
434 _ => None,
435 }
436 }
437
438 pub fn as_u32_slice(&self) -> Option<&[u32]> {
440 match self {
441 Self::U32(v) => Some(v.as_slice()),
442 _ => None,
443 }
444 }
445
446 pub fn as_u32_slice_mut(&mut self) -> Option<&mut [u32]> {
448 match self {
449 Self::U32(v) => Some(v.as_mut_slice()),
450 _ => None,
451 }
452 }
453
454 pub fn as_i32_slice(&self) -> Option<&[i32]> {
456 match self {
457 Self::I32(v) => Some(v.as_slice()),
458 _ => None,
459 }
460 }
461
462 pub fn as_i32_slice_mut(&mut self) -> Option<&mut [i32]> {
464 match self {
465 Self::I32(v) => Some(v.as_mut_slice()),
466 _ => None,
467 }
468 }
469
470 pub fn as_u64_slice(&self) -> Option<&[u64]> {
472 match self {
473 Self::U64(v) => Some(v.as_slice()),
474 _ => None,
475 }
476 }
477
478 pub fn as_u64_slice_mut(&mut self) -> Option<&mut [u64]> {
480 match self {
481 Self::U64(v) => Some(v.as_mut_slice()),
482 _ => None,
483 }
484 }
485
486 pub fn as_i64_slice(&self) -> Option<&[i64]> {
488 match self {
489 Self::I64(v) => Some(v.as_slice()),
490 _ => None,
491 }
492 }
493
494 pub fn as_i64_slice_mut(&mut self) -> Option<&mut [i64]> {
496 match self {
497 Self::I64(v) => Some(v.as_mut_slice()),
498 _ => None,
499 }
500 }
501
502 pub fn as_f32_slice(&self) -> Option<&[f32]> {
504 match self {
505 Self::F32(v) => Some(v.as_slice()),
506 _ => None,
507 }
508 }
509
510 pub fn as_f32_slice_mut(&mut self) -> Option<&mut [f32]> {
512 match self {
513 Self::F32(v) => Some(v.as_mut_slice()),
514 _ => None,
515 }
516 }
517
518 pub fn as_f64_slice(&self) -> Option<&[f64]> {
520 match self {
521 Self::F64(v) => Some(v.as_slice()),
522 _ => None,
523 }
524 }
525
526 pub fn as_f64_slice_mut(&mut self) -> Option<&mut [f64]> {
528 match self {
529 Self::F64(v) => Some(v.as_mut_slice()),
530 _ => None,
531 }
532 }
533
534 pub fn par_fill_with<F>(&mut self, f: F)
541 where
542 F: Fn(usize) -> f64 + Send + Sync,
543 {
544 match self {
545 Self::U8(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i) as u8),
546 Self::I8(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i) as i8),
547 Self::U16(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i) as u16),
548 Self::I16(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i) as i16),
549 Self::U32(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i) as u32),
550 Self::I32(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i) as i32),
551 Self::U64(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i) as u64),
552 Self::I64(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i) as i64),
553 Self::F32(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i) as f32),
554 Self::F64(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i)),
555 }
556 }
557}
558
559#[derive(Debug, Clone, Copy, PartialEq)]
563pub struct NoData(pub f64);
564
565impl NoData {
566 pub const COMMON: NoData = NoData(-9999.0);
568 pub const NAN: NoData = NoData(f64::NAN);
570
571 #[inline]
573 pub fn matches(self, v: f64) -> bool {
574 if self.0.is_nan() {
575 v.is_nan()
576 } else {
577 (v - self.0).abs() < f64::EPSILON * self.0.abs().max(1.0)
578 }
579 }
580}
581
582impl Default for NoData {
583 fn default() -> Self {
584 Self::COMMON
585 }
586}
587
588#[derive(Debug, Clone, Copy, PartialEq)]
592pub struct Extent {
593 pub x_min: f64,
595 pub y_min: f64,
597 pub x_max: f64,
599 pub y_max: f64,
601}
602
603impl Extent {
604 pub fn from_origin(x_min: f64, y_min: f64, cols: usize, rows: usize, cell_size: f64) -> Self {
606 Self {
607 x_min,
608 y_min,
609 x_max: x_min + cols as f64 * cell_size,
610 y_max: y_min + rows as f64 * cell_size,
611 }
612 }
613
614 pub fn width(&self) -> f64 { self.x_max - self.x_min }
616
617 pub fn height(&self) -> f64 { self.y_max - self.y_min }
619}
620
621#[derive(Debug, Clone, Copy, PartialEq)]
625pub struct Statistics {
626 pub min: f64,
628 pub max: f64,
630 pub mean: f64,
632 pub std_dev: f64,
634 pub valid_count: usize,
636 pub nodata_count: usize,
638}
639
640#[derive(Debug, Clone, Copy, PartialEq, Eq)]
642pub enum StatisticsComputationMode {
643 Auto,
645 Scalar,
647 Simd,
649}
650
651#[derive(Debug, Clone, Copy)]
652struct StatsAccumulator {
653 min: f64,
654 max: f64,
655 sum: f64,
656 sum_sq: f64,
657 valid_count: usize,
658 nodata_count: usize,
659}
660
661impl Default for StatsAccumulator {
662 fn default() -> Self {
663 Self {
664 min: f64::INFINITY,
665 max: f64::NEG_INFINITY,
666 sum: 0.0,
667 sum_sq: 0.0,
668 valid_count: 0,
669 nodata_count: 0,
670 }
671 }
672}
673
674impl StatsAccumulator {
675 fn merge(&mut self, other: Self) {
676 self.min = self.min.min(other.min);
677 self.max = self.max.max(other.max);
678 self.sum += other.sum;
679 self.sum_sq += other.sum_sq;
680 self.valid_count += other.valid_count;
681 self.nodata_count += other.nodata_count;
682 }
683
684 fn to_statistics(self) -> Statistics {
685 let (mean, std_dev) = if self.valid_count == 0 {
686 (0.0, 0.0)
687 } else {
688 let n = self.valid_count as f64;
689 let mean = self.sum / n;
690 let variance = (self.sum_sq / n - mean * mean).max(0.0);
691 (mean, variance.sqrt())
692 };
693
694 Statistics {
695 min: if self.valid_count == 0 { 0.0 } else { self.min },
696 max: if self.valid_count == 0 { 0.0 } else { self.max },
697 mean,
698 std_dev,
699 valid_count: self.valid_count,
700 nodata_count: self.nodata_count,
701 }
702 }
703}
704
705#[derive(Debug, Clone, Copy, PartialEq, Eq)]
707pub enum ResampleMethod {
708 Nearest,
710 Bilinear,
712 Cubic,
714 Lanczos,
716 Average,
718 Min,
720 Max,
722 Mode,
724 Median,
726 StdDev,
728}
729
730#[derive(Debug, Clone, Copy, PartialEq, Eq)]
732pub enum NodataPolicy {
733 Strict,
735 PartialKernel,
737 Fill,
739}
740
741#[derive(Debug, Clone, Copy, PartialEq, Eq)]
743pub enum AntimeridianPolicy {
744 Auto,
746 Linear,
748 Wrap,
750}
751
752#[derive(Debug, Clone, Copy, PartialEq, Eq)]
754pub enum GridSizePolicy {
755 Expand,
757 FitInside,
759}
760
761#[derive(Debug, Clone, Copy, PartialEq, Eq)]
763pub enum DestinationFootprint {
764 None,
766 SourceBoundary,
768}
769
770#[derive(Debug, Clone, Copy)]
772pub struct ReprojectOptions {
773 pub dst_epsg: u32,
775 pub resample: ResampleMethod,
777 pub cols: Option<usize>,
779 pub rows: Option<usize>,
781 pub extent: Option<Extent>,
785 pub x_res: Option<f64>,
789 pub y_res: Option<f64>,
793 pub snap_x: Option<f64>,
797 pub snap_y: Option<f64>,
801 pub nodata_policy: NodataPolicy,
803 pub antimeridian_policy: AntimeridianPolicy,
805 pub grid_size_policy: GridSizePolicy,
807 pub destination_footprint: DestinationFootprint,
809 pub warn_on_area_of_use_mismatch: bool,
812}
813
814impl ReprojectOptions {
815 pub fn new(dst_epsg: u32, resample: ResampleMethod) -> Self {
817 Self {
818 dst_epsg,
819 resample,
820 cols: None,
821 rows: None,
822 extent: None,
823 x_res: None,
824 y_res: None,
825 snap_x: None,
826 snap_y: None,
827 nodata_policy: NodataPolicy::PartialKernel,
828 antimeridian_policy: AntimeridianPolicy::Auto,
829 grid_size_policy: GridSizePolicy::Expand,
830 destination_footprint: DestinationFootprint::None,
831 warn_on_area_of_use_mismatch: false,
832 }
833 }
834
835 pub fn with_nodata_policy(mut self, nodata_policy: NodataPolicy) -> Self {
837 self.nodata_policy = nodata_policy;
838 self
839 }
840
841 pub fn with_size(mut self, cols: usize, rows: usize) -> Self {
843 self.cols = Some(cols);
844 self.rows = Some(rows);
845 self
846 }
847
848 pub fn with_extent(mut self, extent: Extent) -> Self {
850 self.extent = Some(extent);
851 self
852 }
853
854 pub fn with_resolution(mut self, x_res: f64, y_res: f64) -> Self {
859 self.x_res = Some(x_res);
860 self.y_res = Some(y_res);
861 self
862 }
863
864 pub fn with_square_resolution(mut self, res: f64) -> Self {
866 self.x_res = Some(res);
867 self.y_res = Some(res);
868 self
869 }
870
871 pub fn with_snap_origin(mut self, snap_x: f64, snap_y: f64) -> Self {
876 self.snap_x = Some(snap_x);
877 self.snap_y = Some(snap_y);
878 self
879 }
880
881 pub fn with_antimeridian_policy(mut self, policy: AntimeridianPolicy) -> Self {
883 self.antimeridian_policy = policy;
884 self
885 }
886
887 pub fn with_grid_size_policy(mut self, policy: GridSizePolicy) -> Self {
889 self.grid_size_policy = policy;
890 self
891 }
892
893 pub fn with_destination_footprint(mut self, footprint: DestinationFootprint) -> Self {
895 self.destination_footprint = footprint;
896 self
897 }
898
899 pub fn with_area_of_use_warning(mut self, enabled: bool) -> Self {
901 self.warn_on_area_of_use_mismatch = enabled;
902 self
903 }
904}
905
906#[derive(Debug, Clone)]
910pub struct RasterConfig {
911 pub cols: usize,
913 pub rows: usize,
915 pub bands: usize,
917 pub x_min: f64,
919 pub y_min: f64,
921 pub cell_size: f64,
923 pub cell_size_y: Option<f64>,
926 pub nodata: f64,
928 pub data_type: DataType,
930 pub crs: CrsInfo,
932 pub metadata: Vec<(String, String)>,
934}
935
936impl Default for RasterConfig {
937 fn default() -> Self {
938 Self {
939 cols: 0,
940 rows: 0,
941 bands: 1,
942 x_min: 0.0,
943 y_min: 0.0,
944 cell_size: 1.0,
945 cell_size_y: None,
946 nodata: -9999.0,
947 data_type: DataType::F32,
948 crs: CrsInfo::default(),
949 metadata: Vec::new(),
950 }
951 }
952}
953
954#[derive(Debug, Clone)]
964pub struct Raster {
965 pub cols: usize,
967 pub rows: usize,
969 pub bands: usize,
971 pub x_min: f64,
973 pub y_min: f64,
975 pub cell_size_x: f64,
977 pub cell_size_y: f64,
979 pub nodata: f64,
981 pub data_type: DataType,
983 pub crs: CrsInfo,
985 pub metadata: Vec<(String, String)>,
987 pub data: RasterData,
989}
990
991impl Raster {
992 pub fn new(cfg: RasterConfig) -> Self {
996 let bands = cfg.bands.max(1);
997 let n = cfg.cols * cfg.rows * bands;
998 let cell_size_y = cfg.cell_size_y.map(|v| v.abs()).unwrap_or(cfg.cell_size);
999 Self {
1000 cols: cfg.cols,
1001 rows: cfg.rows,
1002 bands,
1003 x_min: cfg.x_min,
1004 y_min: cfg.y_min,
1005 cell_size_x: cfg.cell_size,
1006 cell_size_y,
1007 nodata: cfg.nodata,
1008 data_type: cfg.data_type,
1009 crs: cfg.crs,
1010 metadata: cfg.metadata,
1011 data: RasterData::new_filled(cfg.data_type, n, cfg.nodata),
1012 }
1013 }
1014
1015 pub fn from_data(cfg: RasterConfig, data: Vec<f64>) -> Result<Self> {
1020 let bands = cfg.bands.max(1);
1021 if data.len() != cfg.cols * cfg.rows * bands {
1022 return Err(RasterError::InvalidDimensions { cols: cfg.cols, rows: cfg.rows });
1023 }
1024 let dt = cfg.data_type;
1025 let mut r = Self::new(cfg);
1026 r.data = RasterData::from_f64_vec(dt, data);
1027 Ok(r)
1028 }
1029
1030 pub fn from_data_native(cfg: RasterConfig, data: RasterData) -> Result<Self> {
1036 let bands = cfg.bands.max(1);
1037 if data.len() != cfg.cols * cfg.rows * bands {
1038 return Err(RasterError::InvalidDimensions { cols: cfg.cols, rows: cfg.rows });
1039 }
1040 if cfg.data_type != data.data_type() {
1041 return Err(RasterError::Other(format!(
1042 "data_type mismatch: config={}, data={}",
1043 cfg.data_type,
1044 data.data_type()
1045 )));
1046 }
1047 let mut r = Self::new(cfg);
1048 r.data = data;
1049 Ok(r)
1050 }
1051
1052 pub fn new_like(template: &Raster) -> Self {
1056 Self::new(RasterConfig {
1057 cols: template.cols,
1058 rows: template.rows,
1059 bands: template.bands,
1060 x_min: template.x_min,
1061 y_min: template.y_min,
1062 cell_size: template.cell_size_x,
1063 cell_size_y: Some(template.cell_size_y),
1064 nodata: template.nodata,
1065 data_type: template.data_type,
1066 crs: template.crs.clone(),
1067 metadata: template.metadata.clone(),
1068 })
1069 }
1070
1071 pub fn new_like_uninit(template: &Raster) -> Self {
1076 let bands = template.bands.max(1);
1077 let n = template.cols * template.rows * bands;
1078 let cell_size_y = template.cell_size_y;
1079 Self {
1080 cols: template.cols,
1081 rows: template.rows,
1082 bands,
1083 x_min: template.x_min,
1084 y_min: template.y_min,
1085 cell_size_x: template.cell_size_x,
1086 cell_size_y,
1087 nodata: template.nodata,
1088 data_type: template.data_type,
1089 crs: template.crs.clone(),
1090 metadata: template.metadata.clone(),
1091 data: RasterData::new_uninit(template.data_type, n),
1092 }
1093 }
1094
1095 pub fn data_u8(&self) -> Option<&[u8]> { self.data.as_u8_slice() }
1097 pub fn data_u8_mut(&mut self) -> Option<&mut [u8]> { self.data.as_u8_slice_mut() }
1099
1100 pub fn data_i8(&self) -> Option<&[i8]> { self.data.as_i8_slice() }
1102 pub fn data_i8_mut(&mut self) -> Option<&mut [i8]> { self.data.as_i8_slice_mut() }
1104
1105 pub fn data_u16(&self) -> Option<&[u16]> { self.data.as_u16_slice() }
1107 pub fn data_u16_mut(&mut self) -> Option<&mut [u16]> { self.data.as_u16_slice_mut() }
1109
1110 pub fn data_i16(&self) -> Option<&[i16]> { self.data.as_i16_slice() }
1112 pub fn data_i16_mut(&mut self) -> Option<&mut [i16]> { self.data.as_i16_slice_mut() }
1114
1115 pub fn data_u32(&self) -> Option<&[u32]> { self.data.as_u32_slice() }
1117 pub fn data_u32_mut(&mut self) -> Option<&mut [u32]> { self.data.as_u32_slice_mut() }
1119
1120 pub fn data_i32(&self) -> Option<&[i32]> { self.data.as_i32_slice() }
1122 pub fn data_i32_mut(&mut self) -> Option<&mut [i32]> { self.data.as_i32_slice_mut() }
1124
1125 pub fn data_u64(&self) -> Option<&[u64]> { self.data.as_u64_slice() }
1127 pub fn data_u64_mut(&mut self) -> Option<&mut [u64]> { self.data.as_u64_slice_mut() }
1129
1130 pub fn data_i64(&self) -> Option<&[i64]> { self.data.as_i64_slice() }
1132 pub fn data_i64_mut(&mut self) -> Option<&mut [i64]> { self.data.as_i64_slice_mut() }
1134
1135 pub fn data_f32(&self) -> Option<&[f32]> { self.data.as_f32_slice() }
1137 pub fn data_f32_mut(&mut self) -> Option<&mut [f32]> { self.data.as_f32_slice_mut() }
1139
1140 pub fn data_f64(&self) -> Option<&[f64]> { self.data.as_f64_slice() }
1142 pub fn data_f64_mut(&mut self) -> Option<&mut [f64]> { self.data.as_f64_slice_mut() }
1144
1145 pub fn band_view(&self, band: usize) -> BandView {
1155 BandView {
1156 data: self.band_to_vec_f64(band),
1157 rows: self.rows as isize,
1158 cols: self.cols as isize,
1159 nodata: self.nodata,
1160 }
1161 }
1162
1163 #[inline]
1172 pub fn band_as_f64_slice(&self, band: usize) -> Option<&[f64]> {
1173 let stride = self.rows * self.cols;
1174 let start = band * stride;
1175 self.data.as_f64_slice()?.get(start..start + stride)
1176 }
1177
1178 pub fn band_to_vec_f64(&self, band: usize) -> Vec<f64> {
1197 let stride = self.rows * self.cols;
1198 let start = band * stride;
1199 let end = start + stride;
1200 match &self.data {
1201 RasterData::F64(v) => v[start..end].to_vec(),
1202 RasterData::F32(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1203 RasterData::U8(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1204 RasterData::I8(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1205 RasterData::U16(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1206 RasterData::I16(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1207 RasterData::U32(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1208 RasterData::I32(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1209 RasterData::U64(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1210 RasterData::I64(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1211 }
1212 }
1213
1214 pub fn par_fill_with<F>(&mut self, f: F)
1220 where
1221 F: Fn(usize) -> f64 + Send + Sync,
1222 {
1223 self.data.par_fill_with(f);
1224 }
1225
1226 pub fn apply_unary_math<F>(
1237 &mut self,
1238 f: F,
1239 target_bands: Option<Vec<usize>>,
1240 ) -> Result<()>
1241 where
1242 F: Fn(f64) -> f64 + Send + Sync,
1243 {
1244 let nodata = self.nodata;
1245 let nodata_is_nan = nodata.is_nan();
1246
1247 match target_bands {
1248 None => {
1249 if let Some(data) = self.data.as_f32_slice_mut() {
1252 let nodata_f32 = nodata as f32;
1253 data.par_iter_mut().for_each(|v| {
1254 let zf = *v as f64;
1255 let is_nd = if nodata_is_nan { zf.is_nan() } else { *v == nodata_f32 };
1256 if !is_nd {
1257 *v = f(zf) as f32;
1258 }
1259 });
1260 } else if let Some(data) = self.data.as_f64_slice_mut() {
1262 data.par_iter_mut().for_each(|v| {
1263 let is_nd = if nodata_is_nan { v.is_nan() } else { *v == nodata };
1264 if !is_nd {
1265 *v = f(*v);
1266 }
1267 });
1268 } else {
1270 let len = self.data.len();
1271 for i in 0..len {
1272 let z = self.data.get_f64(i);
1273 if nodata_is_nan {
1274 if !z.is_nan() {
1275 self.data.set_f64(i, f(z));
1276 }
1277 } else if z != nodata {
1278 self.data.set_f64(i, f(z));
1279 }
1280 }
1281 }
1282 }
1283 Some(bands) => {
1284 for band in bands {
1286 if band >= self.bands {
1287 return Err(RasterError::OutOfBounds {
1288 band: band as isize,
1289 row: 0,
1290 col: 0,
1291 bands: self.bands,
1292 cols: self.cols,
1293 rows: self.rows,
1294 });
1295 }
1296 let mut vals = self.band_slice(band as isize);
1297 vals.par_iter_mut().for_each(|v| {
1298 let is_nd = if nodata_is_nan { v.is_nan() } else { *v == nodata };
1299 if !is_nd {
1300 *v = f(*v);
1301 }
1302 });
1303 self.set_band_slice(band as isize, &vals)?;
1304 }
1305 }
1306 }
1307
1308 Ok(())
1309 }
1310
1311 pub fn apply_unary_math_from<F>(
1319 &mut self,
1320 f: F,
1321 src: &Raster,
1322 ) -> Result<()>
1323 where
1324 F: Fn(f64) -> f64 + Send + Sync,
1325 {
1326 if self.rows != src.rows || self.cols != src.cols || self.bands != src.bands {
1327 return Err(RasterError::Other(format!(
1328 "raster dimension mismatch: self {}×{}×{} != src {}×{}×{}",
1329 self.bands, self.rows, self.cols, src.bands, src.rows, src.cols
1330 )));
1331 }
1332
1333 let nodata = src.nodata;
1334 let nodata_is_nan = nodata.is_nan();
1335
1336 if let (Some(src_data), Some(dst_data)) = (src.data.as_f32_slice(), self.data.as_f32_slice_mut()) {
1338 let nodata_f32 = nodata as f32;
1339 dst_data.par_iter_mut().zip(src_data.par_iter()).for_each(|(out, &z)| {
1340 let zf = z as f64;
1341 let is_nd = if nodata_is_nan { zf.is_nan() } else { z == nodata_f32 };
1342 *out = if is_nd { nodata_f32 } else { f(zf) as f32 };
1343 });
1344 } else if let (Some(src_data), Some(dst_data)) = (src.data.as_f64_slice(), self.data.as_f64_slice_mut()) {
1346 dst_data.par_iter_mut().zip(src_data.par_iter()).for_each(|(out, &z)| {
1347 let is_nd = if nodata_is_nan { z.is_nan() } else { z == nodata };
1348 *out = if is_nd { nodata } else { f(z) };
1349 });
1350 } else {
1352 let len = self.data.len();
1353 for i in 0..len {
1354 let z = src.data.get_f64(i);
1355 let result = if nodata_is_nan {
1356 if z.is_nan() { nodata } else { f(z) }
1357 } else if z == nodata {
1358 nodata
1359 } else {
1360 f(z)
1361 };
1362 self.data.set_f64(i, result);
1363 }
1364 }
1365
1366 Ok(())
1367 }
1368
1369 pub fn apply_binary_math_from<F>(
1381 &mut self,
1382 f: F,
1383 src1: &Raster,
1384 src2: &Raster,
1385 ) -> Result<()>
1386 where
1387 F: Fn(f64, f64) -> f64 + Send + Sync,
1388 {
1389 if self.rows != src1.rows || self.cols != src1.cols || self.bands != src1.bands
1390 || src1.rows != src2.rows || src1.cols != src2.cols || src1.bands != src2.bands
1391 {
1392 return Err(RasterError::Other(format!(
1393 "raster dimension mismatch: self {}×{}×{}, src1 {}×{}×{}, src2 {}×{}×{}",
1394 self.bands, self.rows, self.cols,
1395 src1.bands, src1.rows, src1.cols,
1396 src2.bands, src2.rows, src2.cols,
1397 )));
1398 }
1399
1400 let nd1 = src1.nodata;
1401 let nd1_is_nan = nd1.is_nan();
1402 let nd2 = src2.nodata;
1403 let nd2_is_nan = nd2.is_nan();
1404 let nd_out = self.nodata;
1405
1406 let is_nd1 = |v: f32| -> bool {
1407 if nd1_is_nan { (v as f64).is_nan() } else { v == nd1 as f32 }
1408 };
1409 let is_nd2 = |v: f32| -> bool {
1410 if nd2_is_nan { (v as f64).is_nan() } else { v == nd2 as f32 }
1411 };
1412 let is_nd1_f64 = |v: f64| -> bool {
1413 if nd1_is_nan { v.is_nan() } else { v == nd1 }
1414 };
1415 let is_nd2_f64 = |v: f64| -> bool {
1416 if nd2_is_nan { v.is_nan() } else { v == nd2 }
1417 };
1418
1419 if let (Some(d1), Some(dst)) = (src1.data.as_f32_slice(), self.data.as_f32_slice_mut()) {
1421 let nd_out_f32 = nd_out as f32;
1422
1423 if let Some(d2) = src2.data.as_f32_slice() {
1424 dst.par_iter_mut()
1425 .zip(d1.par_iter())
1426 .zip(d2.par_iter())
1427 .for_each(|((out, &z1), &z2)| {
1428 if is_nd1(z1) || is_nd2(z2) {
1429 *out = nd_out_f32;
1430 } else {
1431 *out = f(z1 as f64, z2 as f64) as f32;
1432 }
1433 });
1434 return Ok(());
1435 }
1436
1437 macro_rules! run_f32_rhs_typed {
1438 ($rhs:expr) => {{
1439 dst.par_iter_mut()
1440 .zip(d1.par_iter())
1441 .zip($rhs.par_iter())
1442 .for_each(|((out, &z1), &z2)| {
1443 let z2f = z2 as f64;
1444 let z2_is_nd = if nd2_is_nan { z2f.is_nan() } else { z2f == nd2 };
1445 if is_nd1(z1) || z2_is_nd {
1446 *out = nd_out_f32;
1447 } else {
1448 *out = f(z1 as f64, z2f) as f32;
1449 }
1450 });
1451 return Ok(());
1452 }};
1453 }
1454
1455 if let Some(d2) = src2.data.as_f64_slice() {
1456 dst.par_iter_mut()
1457 .zip(d1.par_iter())
1458 .zip(d2.par_iter())
1459 .for_each(|((out, &z1), &z2)| {
1460 if is_nd1(z1) || is_nd2_f64(z2) {
1461 *out = nd_out_f32;
1462 } else {
1463 *out = f(z1 as f64, z2) as f32;
1464 }
1465 });
1466 return Ok(());
1467 }
1468 if let Some(d2) = src2.data.as_u8_slice() { run_f32_rhs_typed!(d2); }
1469 if let Some(d2) = src2.data.as_i8_slice() { run_f32_rhs_typed!(d2); }
1470 if let Some(d2) = src2.data.as_u16_slice() { run_f32_rhs_typed!(d2); }
1471 if let Some(d2) = src2.data.as_i16_slice() { run_f32_rhs_typed!(d2); }
1472 if let Some(d2) = src2.data.as_u32_slice() { run_f32_rhs_typed!(d2); }
1473 if let Some(d2) = src2.data.as_i32_slice() { run_f32_rhs_typed!(d2); }
1474 if let Some(d2) = src2.data.as_u64_slice() { run_f32_rhs_typed!(d2); }
1475 if let Some(d2) = src2.data.as_i64_slice() { run_f32_rhs_typed!(d2); }
1476 }
1477
1478 if let (Some(d1), Some(dst)) = (src1.data.as_f64_slice(), self.data.as_f64_slice_mut()) {
1480 if let Some(d2) = src2.data.as_f64_slice() {
1481 dst.par_iter_mut()
1482 .zip(d1.par_iter())
1483 .zip(d2.par_iter())
1484 .for_each(|((out, &z1), &z2)| {
1485 if is_nd1_f64(z1) || is_nd2_f64(z2) {
1486 *out = nd_out;
1487 } else {
1488 *out = f(z1, z2);
1489 }
1490 });
1491 return Ok(());
1492 }
1493
1494 macro_rules! run_f64_rhs_typed {
1495 ($rhs:expr) => {{
1496 dst.par_iter_mut()
1497 .zip(d1.par_iter())
1498 .zip($rhs.par_iter())
1499 .for_each(|((out, &z1), &z2)| {
1500 let z2f = z2 as f64;
1501 let z2_is_nd = if nd2_is_nan { z2f.is_nan() } else { z2f == nd2 };
1502 if is_nd1_f64(z1) || z2_is_nd {
1503 *out = nd_out;
1504 } else {
1505 *out = f(z1, z2f);
1506 }
1507 });
1508 return Ok(());
1509 }};
1510 }
1511
1512 if let Some(d2) = src2.data.as_f32_slice() {
1513 dst.par_iter_mut()
1514 .zip(d1.par_iter())
1515 .zip(d2.par_iter())
1516 .for_each(|((out, &z1), &z2)| {
1517 if is_nd1_f64(z1) || is_nd2(z2) {
1518 *out = nd_out;
1519 } else {
1520 *out = f(z1, z2 as f64);
1521 }
1522 });
1523 return Ok(());
1524 }
1525 if let Some(d2) = src2.data.as_u8_slice() { run_f64_rhs_typed!(d2); }
1526 if let Some(d2) = src2.data.as_i8_slice() { run_f64_rhs_typed!(d2); }
1527 if let Some(d2) = src2.data.as_u16_slice() { run_f64_rhs_typed!(d2); }
1528 if let Some(d2) = src2.data.as_i16_slice() { run_f64_rhs_typed!(d2); }
1529 if let Some(d2) = src2.data.as_u32_slice() { run_f64_rhs_typed!(d2); }
1530 if let Some(d2) = src2.data.as_i32_slice() { run_f64_rhs_typed!(d2); }
1531 if let Some(d2) = src2.data.as_u64_slice() { run_f64_rhs_typed!(d2); }
1532 if let Some(d2) = src2.data.as_i64_slice() { run_f64_rhs_typed!(d2); }
1533 }
1534
1535 if let (Some(d1), Some(dst)) = (src1.data.as_i16_slice(), self.data.as_i16_slice_mut()) {
1538 let nd1_i16 = nd1 as i16;
1539 let nd_out_i16 = nd_out as i16;
1540 let chk1_i16 = |v: i16| v == nd1_i16;
1541
1542 macro_rules! run_i16_rhs_typed {
1543 ($rhs:expr) => {{
1544 dst.par_iter_mut()
1545 .zip(d1.par_iter())
1546 .zip($rhs.par_iter())
1547 .for_each(|((out, &z1), &z2)| {
1548 let z2f = z2 as f64;
1549 let z2_is_nd = if nd2_is_nan { z2f.is_nan() } else { z2f == nd2 };
1550 *out = if chk1_i16(z1) || z2_is_nd {
1551 nd_out_i16
1552 } else {
1553 f(z1 as f64, z2f) as i16
1554 };
1555 });
1556 return Ok(());
1557 }};
1558 }
1559
1560 if let Some(d2) = src2.data.as_f32_slice() {
1561 dst.par_iter_mut()
1562 .zip(d1.par_iter())
1563 .zip(d2.par_iter())
1564 .for_each(|((out, &z1), &z2)| {
1565 *out = if chk1_i16(z1) || is_nd2(z2) {
1566 nd_out_i16
1567 } else {
1568 f(z1 as f64, z2 as f64) as i16
1569 };
1570 });
1571 return Ok(());
1572 }
1573 if let Some(d2) = src2.data.as_f64_slice() { run_i16_rhs_typed!(d2); }
1574 if let Some(d2) = src2.data.as_u8_slice() { run_i16_rhs_typed!(d2); }
1575 if let Some(d2) = src2.data.as_i8_slice() { run_i16_rhs_typed!(d2); }
1576 if let Some(d2) = src2.data.as_u16_slice() { run_i16_rhs_typed!(d2); }
1577 if let Some(d2) = src2.data.as_i16_slice() { run_i16_rhs_typed!(d2); }
1578 if let Some(d2) = src2.data.as_u32_slice() { run_i16_rhs_typed!(d2); }
1579 if let Some(d2) = src2.data.as_i32_slice() { run_i16_rhs_typed!(d2); }
1580 if let Some(d2) = src2.data.as_u64_slice() { run_i16_rhs_typed!(d2); }
1581 if let Some(d2) = src2.data.as_i64_slice() { run_i16_rhs_typed!(d2); }
1582 }
1583
1584 if let (Some(d1), Some(dst)) = (src1.data.as_u8_slice(), self.data.as_u8_slice_mut()) {
1587 let nd1_u8 = nd1 as u8;
1588 let nd_out_u8 = nd_out as u8;
1589 let chk1_u8 = |v: u8| v == nd1_u8;
1590
1591 macro_rules! run_u8_rhs_typed {
1592 ($rhs:expr) => {{
1593 dst.par_iter_mut()
1594 .zip(d1.par_iter())
1595 .zip($rhs.par_iter())
1596 .for_each(|((out, &z1), &z2)| {
1597 let z2f = z2 as f64;
1598 let z2_is_nd = if nd2_is_nan { z2f.is_nan() } else { z2f == nd2 };
1599 *out = if chk1_u8(z1) || z2_is_nd {
1600 nd_out_u8
1601 } else {
1602 f(z1 as f64, z2f) as u8
1603 };
1604 });
1605 return Ok(());
1606 }};
1607 }
1608
1609 if let Some(d2) = src2.data.as_f32_slice() {
1610 dst.par_iter_mut()
1611 .zip(d1.par_iter())
1612 .zip(d2.par_iter())
1613 .for_each(|((out, &z1), &z2)| {
1614 *out = if chk1_u8(z1) || is_nd2(z2) {
1615 nd_out_u8
1616 } else {
1617 f(z1 as f64, z2 as f64) as u8
1618 };
1619 });
1620 return Ok(());
1621 }
1622 if let Some(d2) = src2.data.as_f64_slice() { run_u8_rhs_typed!(d2); }
1623 if let Some(d2) = src2.data.as_u8_slice() { run_u8_rhs_typed!(d2); }
1624 if let Some(d2) = src2.data.as_i8_slice() { run_u8_rhs_typed!(d2); }
1625 if let Some(d2) = src2.data.as_u16_slice() { run_u8_rhs_typed!(d2); }
1626 if let Some(d2) = src2.data.as_i16_slice() { run_u8_rhs_typed!(d2); }
1627 if let Some(d2) = src2.data.as_u32_slice() { run_u8_rhs_typed!(d2); }
1628 if let Some(d2) = src2.data.as_i32_slice() { run_u8_rhs_typed!(d2); }
1629 if let Some(d2) = src2.data.as_u64_slice() { run_u8_rhs_typed!(d2); }
1630 if let Some(d2) = src2.data.as_i64_slice() { run_u8_rhs_typed!(d2); }
1631 }
1632
1633 self.data.par_fill_with(|i| {
1638 let z1 = src1.data.get_f64(i);
1639 let z2 = src2.data.get_f64(i);
1640 if is_nd1_f64(z1) || is_nd2_f64(z2) { nd_out } else { f(z1, z2) }
1641 });
1642
1643 Ok(())
1644 }
1645
1646 pub fn apply_scalar_add(&mut self, src: &Raster, scalar: f64) -> Result<()> {
1654 self.apply_unary_math_from(|z| z + scalar, src)
1655 }
1656
1657 pub fn apply_scalar_sub(&mut self, src: &Raster, scalar: f64) -> Result<()> {
1665 self.apply_unary_math_from(|z| z - scalar, src)
1666 }
1667
1668 #[inline]
1673 pub fn index(&self, band: isize, row: isize, col: isize) -> Option<usize> {
1674 if band < 0
1675 || row < 0
1676 || col < 0
1677 || band >= self.bands as isize
1678 || row >= self.rows as isize
1679 || col >= self.cols as isize
1680 {
1681 return None;
1682 }
1683 let band = band as usize;
1684 let row = row as usize;
1685 let col = col as usize;
1686 let band_stride = self.rows * self.cols;
1687 Some(band * band_stride + row * self.cols + col)
1688 }
1689
1690 #[inline]
1695 pub fn get(&self, band: isize, row: isize, col: isize) -> f64 {
1696 self.get_raw(band, row, col).unwrap_or(self.nodata)
1697 }
1698
1699 #[inline]
1704 pub fn get_opt(&self, band: isize, row: isize, col: isize) -> Option<f64> {
1705 let idx = self.index(band, row, col)?;
1706 let v = self.data.get_f64(idx);
1707 if self.is_nodata(v) { None } else { Some(v) }
1708 }
1709
1710 #[inline]
1713 pub fn get_raw(&self, band: isize, row: isize, col: isize) -> Option<f64> {
1714 let idx = self.index(band, row, col)?;
1715 Some(self.data.get_f64(idx))
1716 }
1717
1718 #[inline]
1723 pub fn set(&mut self, band: isize, row: isize, col: isize, value: f64) -> Result<()> {
1724 if band < 0
1725 || row < 0
1726 || col < 0
1727 || band >= self.bands as isize
1728 || row >= self.rows as isize
1729 || col >= self.cols as isize
1730 {
1731 return Err(RasterError::OutOfBounds {
1732 band,
1733 col,
1734 row,
1735 bands: self.bands,
1736 cols: self.cols,
1737 rows: self.rows,
1738 });
1739 }
1740 let idx = self.index(band, row, col).expect("set bounds prechecked");
1741 self.data.set_f64(idx, value);
1742 Ok(())
1743 }
1744
1745 #[inline]
1747 pub fn set_unchecked(&mut self, band: isize, row: isize, col: isize, value: f64) {
1748 let idx = self
1749 .index(band, row, col)
1750 .expect("set_unchecked requires in-bounds coordinates");
1751 self.data.set_f64(idx, value);
1752 }
1753
1754 #[inline]
1756 pub fn is_nodata(&self, v: f64) -> bool {
1757 if self.nodata.is_nan() {
1758 v.is_nan()
1759 } else {
1760 (v - self.nodata).abs() < 1e-10 * self.nodata.abs().max(1.0)
1761 }
1762 }
1763
1764 #[inline]
1768 pub fn y_max(&self) -> f64 {
1769 self.y_min + self.rows as f64 * self.cell_size_y
1770 }
1771
1772 #[inline]
1774 pub fn x_max(&self) -> f64 {
1775 self.x_min + self.cols as f64 * self.cell_size_x
1776 }
1777
1778 pub fn extent(&self) -> Extent {
1780 Extent {
1781 x_min: self.x_min,
1782 y_min: self.y_min,
1783 x_max: self.x_max(),
1784 y_max: self.y_max(),
1785 }
1786 }
1787
1788 #[inline]
1790 pub fn col_center_x(&self, col: isize) -> f64 {
1791 self.x_min + (col as f64 + 0.5) * self.cell_size_x
1792 }
1793
1794 #[inline]
1796 pub fn row_center_y(&self, row: isize) -> f64 {
1797 self.y_max() - (row as f64 + 0.5) * self.cell_size_y
1798 }
1799
1800 pub fn world_to_pixel(&self, x: f64, y: f64) -> Option<(isize, isize)> {
1803 if x < self.x_min || x >= self.x_max() || y < self.y_min || y >= self.y_max() {
1804 return None;
1805 }
1806 let col = ((x - self.x_min) / self.cell_size_x).floor() as isize;
1807 let row = ((self.y_max() - y) / self.cell_size_y).floor() as isize;
1808 let col = col.min(self.cols as isize - 1);
1809 let row = row.min(self.rows as isize - 1);
1810 Some((col, row))
1811 }
1812
1813 pub fn assign_crs_epsg(&mut self, epsg: u32) {
1818 self.crs = CrsInfo {
1819 epsg: Some(epsg),
1820 wkt: None,
1821 proj4: None,
1822 };
1823 }
1824
1825 pub fn assign_crs_wkt(&mut self, wkt: &str) {
1830 self.crs = CrsInfo {
1831 epsg: None,
1832 wkt: Some(wkt.to_string()),
1833 proj4: None,
1834 };
1835 }
1836
1837 pub fn reproject_to_epsg(&self, dst_epsg: u32, resample: ResampleMethod) -> Result<Raster> {
1851 self.reproject_with_options(&ReprojectOptions::new(dst_epsg, resample))
1852 }
1853
1854 pub fn reproject_with_options(&self, options: &ReprojectOptions) -> Result<Raster> {
1856 let src_crs = self.source_crs_for_reprojection()?;
1857 let dst_crs = Crs::from_epsg(options.dst_epsg).map_err(|e| {
1858 RasterError::Other(format!(
1859 "destination EPSG {} is not supported: {e}",
1860 options.dst_epsg
1861 ))
1862 })?;
1863
1864 self.reproject_internal(&src_crs, &dst_crs, options, None)
1865 }
1866
1867 pub fn reproject_with_options_and_progress<F>(
1870 &self,
1871 options: &ReprojectOptions,
1872 progress: F,
1873 ) -> Result<Raster>
1874 where
1875 F: Fn(f64) + Send + Sync,
1876 {
1877 let src_crs = self.source_crs_for_reprojection()?;
1878 let dst_crs = Crs::from_epsg(options.dst_epsg).map_err(|e| {
1879 RasterError::Other(format!(
1880 "destination EPSG {} is not supported: {e}",
1881 options.dst_epsg
1882 ))
1883 })?;
1884
1885 self.reproject_internal(&src_crs, &dst_crs, options, Some(&progress))
1886 }
1887
1888 pub fn reproject_with_crs(
1896 &self,
1897 src_crs: &Crs,
1898 dst_crs: &Crs,
1899 options: &ReprojectOptions,
1900 ) -> Result<Raster> {
1901 self.reproject_internal(src_crs, dst_crs, options, None)
1902 }
1903
1904 pub fn reproject_with_crs_and_progress<F>(
1907 &self,
1908 src_crs: &Crs,
1909 dst_crs: &Crs,
1910 options: &ReprojectOptions,
1911 progress: F,
1912 ) -> Result<Raster>
1913 where
1914 F: Fn(f64) + Send + Sync,
1915 {
1916 self.reproject_internal(src_crs, dst_crs, options, Some(&progress))
1917 }
1918
1919 pub fn reproject_to_epsg_nearest(&self, dst_epsg: u32) -> Result<Raster> {
1921 self.reproject_to_epsg(dst_epsg, ResampleMethod::Nearest)
1922 }
1923
1924 pub fn reproject_to_epsg_bilinear(&self, dst_epsg: u32) -> Result<Raster> {
1926 self.reproject_to_epsg(dst_epsg, ResampleMethod::Bilinear)
1927 }
1928
1929 pub fn reproject_to_epsg_cubic(&self, dst_epsg: u32) -> Result<Raster> {
1931 self.reproject_to_epsg(dst_epsg, ResampleMethod::Cubic)
1932 }
1933
1934 pub fn reproject_to_epsg_lanczos(&self, dst_epsg: u32) -> Result<Raster> {
1936 self.reproject_to_epsg(dst_epsg, ResampleMethod::Lanczos)
1937 }
1938
1939 pub fn reproject_to_epsg_average(&self, dst_epsg: u32) -> Result<Raster> {
1941 self.reproject_to_epsg(dst_epsg, ResampleMethod::Average)
1942 }
1943
1944 pub fn reproject_to_epsg_min(&self, dst_epsg: u32) -> Result<Raster> {
1946 self.reproject_to_epsg(dst_epsg, ResampleMethod::Min)
1947 }
1948
1949 pub fn reproject_to_epsg_max(&self, dst_epsg: u32) -> Result<Raster> {
1951 self.reproject_to_epsg(dst_epsg, ResampleMethod::Max)
1952 }
1953
1954 pub fn reproject_to_epsg_mode(&self, dst_epsg: u32) -> Result<Raster> {
1956 self.reproject_to_epsg(dst_epsg, ResampleMethod::Mode)
1957 }
1958
1959 pub fn reproject_to_epsg_median(&self, dst_epsg: u32) -> Result<Raster> {
1961 self.reproject_to_epsg(dst_epsg, ResampleMethod::Median)
1962 }
1963
1964 pub fn reproject_to_epsg_stddev(&self, dst_epsg: u32) -> Result<Raster> {
1966 self.reproject_to_epsg(dst_epsg, ResampleMethod::StdDev)
1967 }
1968
1969 pub fn reproject_to_match_grid(
1978 &self,
1979 target_grid: &Raster,
1980 resample: ResampleMethod,
1981 ) -> Result<Raster> {
1982 let dst_epsg = target_grid.crs.epsg.ok_or_else(|| {
1983 RasterError::Other(
1984 "reproject_to_match_grid requires target grid CRS EPSG in target_grid.crs.epsg"
1985 .to_string(),
1986 )
1987 })?;
1988
1989 let opts = ReprojectOptions::new(dst_epsg, resample)
1990 .with_size(target_grid.cols, target_grid.rows)
1991 .with_extent(target_grid.extent());
1992
1993 self.reproject_with_options(&opts)
1994 }
1995
1996 pub fn reproject_to_match_grid_and_progress<F>(
1999 &self,
2000 target_grid: &Raster,
2001 resample: ResampleMethod,
2002 progress: F,
2003 ) -> Result<Raster>
2004 where
2005 F: Fn(f64) + Send + Sync,
2006 {
2007 let dst_epsg = target_grid.crs.epsg.ok_or_else(|| {
2008 RasterError::Other(
2009 "reproject_to_match_grid requires target grid CRS EPSG in target_grid.crs.epsg"
2010 .to_string(),
2011 )
2012 })?;
2013
2014 let opts = ReprojectOptions::new(dst_epsg, resample)
2015 .with_size(target_grid.cols, target_grid.rows)
2016 .with_extent(target_grid.extent());
2017
2018 self.reproject_with_options_and_progress(&opts, progress)
2019 }
2020
2021 pub fn reproject_to_match_resolution(
2030 &self,
2031 reference_grid: &Raster,
2032 resample: ResampleMethod,
2033 ) -> Result<Raster> {
2034 let dst_epsg = reference_grid.crs.epsg.ok_or_else(|| {
2035 RasterError::Other(
2036 "reproject_to_match_resolution requires reference grid CRS EPSG in reference_grid.crs.epsg"
2037 .to_string(),
2038 )
2039 })?;
2040
2041 let opts = ReprojectOptions::new(dst_epsg, resample)
2042 .with_resolution(reference_grid.cell_size_x, reference_grid.cell_size_y)
2043 .with_snap_origin(reference_grid.x_min, reference_grid.y_min);
2044
2045 self.reproject_with_options(&opts)
2046 }
2047
2048 pub fn reproject_to_match_resolution_and_progress<F>(
2051 &self,
2052 reference_grid: &Raster,
2053 resample: ResampleMethod,
2054 progress: F,
2055 ) -> Result<Raster>
2056 where
2057 F: Fn(f64) + Send + Sync,
2058 {
2059 let dst_epsg = reference_grid.crs.epsg.ok_or_else(|| {
2060 RasterError::Other(
2061 "reproject_to_match_resolution requires reference grid CRS EPSG in reference_grid.crs.epsg"
2062 .to_string(),
2063 )
2064 })?;
2065
2066 let opts = ReprojectOptions::new(dst_epsg, resample)
2067 .with_resolution(reference_grid.cell_size_x, reference_grid.cell_size_y)
2068 .with_snap_origin(reference_grid.x_min, reference_grid.y_min);
2069
2070 self.reproject_with_options_and_progress(&opts, progress)
2071 }
2072
2073 pub fn reproject_to_match_resolution_in_epsg(
2084 &self,
2085 dst_epsg: u32,
2086 reference_grid: &Raster,
2087 resample: ResampleMethod,
2088 ) -> Result<Raster> {
2089 let reference_epsg = reference_grid.crs.epsg.ok_or_else(|| {
2090 RasterError::Other(
2091 "reproject_to_match_resolution_in_epsg requires reference grid CRS EPSG in reference_grid.crs.epsg"
2092 .to_string(),
2093 )
2094 })?;
2095
2096 let (snap_x, snap_y, x_res, y_res) = if reference_epsg == dst_epsg {
2097 (
2098 reference_grid.x_min,
2099 reference_grid.y_min,
2100 reference_grid.cell_size_x,
2101 reference_grid.cell_size_y,
2102 )
2103 } else {
2104 let ref_crs = Crs::from_epsg(reference_epsg).map_err(|e| {
2105 RasterError::Other(format!(
2106 "reference EPSG {reference_epsg} is not supported: {e}"
2107 ))
2108 })?;
2109 let dst_crs = Crs::from_epsg(dst_epsg).map_err(|e| {
2110 RasterError::Other(format!(
2111 "destination EPSG {dst_epsg} is not supported: {e}"
2112 ))
2113 })?;
2114
2115 let ox = reference_grid.x_min;
2116 let oy = reference_grid.y_min;
2117 let (sx, sy) = ref_crs.transform_to(ox, oy, &dst_crs).map_err(|e| {
2118 RasterError::Other(format!(
2119 "failed to transform reference snap origin to EPSG:{dst_epsg}: {e}"
2120 ))
2121 })?;
2122 let (sx_dx, sy_dx) = ref_crs
2123 .transform_to(ox + reference_grid.cell_size_x, oy, &dst_crs)
2124 .map_err(|e| {
2125 RasterError::Other(format!(
2126 "failed to transform reference X-step to EPSG:{dst_epsg}: {e}"
2127 ))
2128 })?;
2129 let (sx_dy, sy_dy) = ref_crs
2130 .transform_to(ox, oy + reference_grid.cell_size_y, &dst_crs)
2131 .map_err(|e| {
2132 RasterError::Other(format!(
2133 "failed to transform reference Y-step to EPSG:{dst_epsg}: {e}"
2134 ))
2135 })?;
2136
2137 let rx = (sx_dx - sx).hypot(sy_dx - sy);
2138 let ry = (sx_dy - sx).hypot(sy_dy - sy);
2139 if !rx.is_finite() || !ry.is_finite() || rx <= 0.0 || ry <= 0.0 {
2140 return Err(RasterError::Other(
2141 "invalid transformed reference resolution while matching destination EPSG"
2142 .to_string(),
2143 ));
2144 }
2145 (sx, sy, rx, ry)
2146 };
2147
2148 let opts = ReprojectOptions::new(dst_epsg, resample)
2149 .with_resolution(x_res, y_res)
2150 .with_snap_origin(snap_x, snap_y);
2151
2152 self.reproject_with_options(&opts)
2153 }
2154
2155 pub fn reproject_to_match_resolution_in_epsg_and_progress<F>(
2158 &self,
2159 dst_epsg: u32,
2160 reference_grid: &Raster,
2161 resample: ResampleMethod,
2162 progress: F,
2163 ) -> Result<Raster>
2164 where
2165 F: Fn(f64) + Send + Sync,
2166 {
2167 let reference_epsg = reference_grid.crs.epsg.ok_or_else(|| {
2168 RasterError::Other(
2169 "reproject_to_match_resolution_in_epsg requires reference grid CRS EPSG in reference_grid.crs.epsg"
2170 .to_string(),
2171 )
2172 })?;
2173
2174 let (snap_x, snap_y, x_res, y_res) = if reference_epsg == dst_epsg {
2175 (
2176 reference_grid.x_min,
2177 reference_grid.y_min,
2178 reference_grid.cell_size_x,
2179 reference_grid.cell_size_y,
2180 )
2181 } else {
2182 let ref_crs = Crs::from_epsg(reference_epsg).map_err(|e| {
2183 RasterError::Other(format!(
2184 "reference EPSG {reference_epsg} is not supported: {e}"
2185 ))
2186 })?;
2187 let dst_crs = Crs::from_epsg(dst_epsg).map_err(|e| {
2188 RasterError::Other(format!(
2189 "destination EPSG {dst_epsg} is not supported: {e}"
2190 ))
2191 })?;
2192
2193 let ox = reference_grid.x_min;
2194 let oy = reference_grid.y_min;
2195 let (sx, sy) = ref_crs.transform_to(ox, oy, &dst_crs).map_err(|e| {
2196 RasterError::Other(format!(
2197 "failed to transform reference snap origin to EPSG:{dst_epsg}: {e}"
2198 ))
2199 })?;
2200 let (sx_dx, sy_dx) = ref_crs
2201 .transform_to(ox + reference_grid.cell_size_x, oy, &dst_crs)
2202 .map_err(|e| {
2203 RasterError::Other(format!(
2204 "failed to transform reference X-step to EPSG:{dst_epsg}: {e}"
2205 ))
2206 })?;
2207 let (sx_dy, sy_dy) = ref_crs
2208 .transform_to(ox, oy + reference_grid.cell_size_y, &dst_crs)
2209 .map_err(|e| {
2210 RasterError::Other(format!(
2211 "failed to transform reference Y-step to EPSG:{dst_epsg}: {e}"
2212 ))
2213 })?;
2214
2215 let rx = (sx_dx - sx).hypot(sy_dx - sy);
2216 let ry = (sx_dy - sx).hypot(sy_dy - sy);
2217 if !rx.is_finite() || !ry.is_finite() || rx <= 0.0 || ry <= 0.0 {
2218 return Err(RasterError::Other(
2219 "invalid transformed reference resolution while matching destination EPSG"
2220 .to_string(),
2221 ));
2222 }
2223 (sx, sy, rx, ry)
2224 };
2225
2226 let opts = ReprojectOptions::new(dst_epsg, resample)
2227 .with_resolution(x_res, y_res)
2228 .with_snap_origin(snap_x, snap_y);
2229
2230 self.reproject_with_options_and_progress(&opts, progress)
2231 }
2232
2233 fn reproject_internal(
2234 &self,
2235 src_crs: &Crs,
2236 dst_crs: &Crs,
2237 options: &ReprojectOptions,
2238 progress: Option<&(dyn Fn(f64) + Send + Sync)>,
2239 ) -> Result<Raster> {
2240 maybe_warn_area_of_use_mismatch(
2241 src_crs,
2242 dst_crs,
2243 self.extent(),
2244 options.warn_on_area_of_use_mismatch,
2245 );
2246
2247 let src_extent = self.extent();
2248 let samples_per_edge = (self.cols.max(self.rows) / 32).clamp(8, 128);
2249 let base_extent = transformed_extent_from_boundary_samples(
2250 src_crs,
2251 dst_crs,
2252 src_extent,
2253 samples_per_edge,
2254 options.dst_epsg,
2255 options.antimeridian_policy,
2256 )?;
2257 let out_extent = options.extent.unwrap_or(base_extent);
2258 let width = out_extent.x_max - out_extent.x_min;
2259 let height = out_extent.y_max - out_extent.y_min;
2260
2261 if !out_extent.x_min.is_finite()
2262 || !out_extent.x_max.is_finite()
2263 || !out_extent.y_min.is_finite()
2264 || !out_extent.y_max.is_finite()
2265 || width <= 0.0
2266 || height <= 0.0
2267 {
2268 return Err(RasterError::CorruptData(
2269 "invalid transformed extent produced during reprojection".to_string(),
2270 ));
2271 }
2272
2273 let x_res = options.x_res.map(f64::abs);
2274 let y_res = options.y_res.map(f64::abs);
2275 if x_res.is_some_and(|v| !v.is_finite() || v <= 0.0)
2276 || y_res.is_some_and(|v| !v.is_finite() || v <= 0.0)
2277 {
2278 return Err(RasterError::CorruptData(
2279 "invalid reprojection resolution (x_res/y_res must be positive finite values)"
2280 .to_string(),
2281 ));
2282 }
2283
2284 let mut x_min = out_extent.x_min;
2285 let mut x_max = out_extent.x_max;
2286 let mut y_min = out_extent.y_min;
2287 let mut y_max = out_extent.y_max;
2288
2289 let out_cols = match options.cols {
2290 Some(cols) => cols,
2291 None => match x_res {
2292 Some(rx) => {
2293 if let Some(sx) = options.snap_x {
2294 match options.grid_size_policy {
2295 GridSizePolicy::Expand => {
2296 x_min = snap_down_to_origin(x_min, sx, rx);
2297 x_max = snap_up_to_origin(x_max, sx, rx);
2298 }
2299 GridSizePolicy::FitInside => {
2300 x_min = snap_up_to_origin(x_min, sx, rx);
2301 x_max = snap_down_to_origin(x_max, sx, rx);
2302 }
2303 }
2304 }
2305 let span = (x_max - x_min).max(0.0);
2306 let cols = match options.grid_size_policy {
2307 GridSizePolicy::Expand => (span / rx).ceil().max(1.0) as usize,
2308 GridSizePolicy::FitInside => (span / rx).floor().max(1.0) as usize,
2309 };
2310 x_max = x_min + cols as f64 * rx;
2311 cols
2312 }
2313 None => self.cols,
2314 },
2315 };
2316 let out_rows = match options.rows {
2317 Some(rows) => rows,
2318 None => match y_res {
2319 Some(ry) => {
2320 if let Some(sy) = options.snap_y {
2321 match options.grid_size_policy {
2322 GridSizePolicy::Expand => {
2323 y_min = snap_down_to_origin(y_min, sy, ry);
2324 y_max = snap_up_to_origin(y_max, sy, ry);
2325 }
2326 GridSizePolicy::FitInside => {
2327 y_min = snap_up_to_origin(y_min, sy, ry);
2328 y_max = snap_down_to_origin(y_max, sy, ry);
2329 }
2330 }
2331 }
2332 let span = (y_max - y_min).max(0.0);
2333 let rows = match options.grid_size_policy {
2334 GridSizePolicy::Expand => (span / ry).ceil().max(1.0) as usize,
2335 GridSizePolicy::FitInside => (span / ry).floor().max(1.0) as usize,
2336 };
2337 y_max = y_min + rows as f64 * ry;
2338 rows
2339 }
2340 None => self.rows,
2341 },
2342 };
2343
2344 let out_extent = Extent {
2345 x_min,
2346 y_min,
2347 x_max,
2348 y_max,
2349 };
2350
2351 if out_cols == 0 || out_rows == 0 {
2352 return Err(RasterError::InvalidDimensions {
2353 cols: out_cols,
2354 rows: out_rows,
2355 });
2356 }
2357
2358 let cfg = RasterConfig {
2359 cols: out_cols,
2360 rows: out_rows,
2361 bands: self.bands,
2362 x_min: out_extent.x_min,
2363 y_min: out_extent.y_min,
2364 cell_size: (out_extent.x_max - out_extent.x_min) / out_cols as f64,
2365 cell_size_y: Some((out_extent.y_max - out_extent.y_min) / out_rows as f64),
2366 nodata: self.nodata,
2367 data_type: self.data_type,
2368 crs: CrsInfo::from_epsg(options.dst_epsg),
2369 metadata: self.metadata.clone(),
2370 };
2371 let mut out = Raster::new(cfg);
2372
2373 let out_y_max = out.y_max();
2374 let footprint_ring = if options.destination_footprint == DestinationFootprint::SourceBoundary {
2375 let ring = transformed_boundary_ring_samples(
2376 src_crs,
2377 dst_crs,
2378 src_extent,
2379 samples_per_edge,
2380 options.dst_epsg,
2381 options.antimeridian_policy,
2382 )?;
2383 if ring.len() >= 3 {
2384 Some(ring)
2385 } else {
2386 None
2387 }
2388 } else {
2389 None
2390 };
2391
2392 let total_rows = out.rows;
2393 let completed_rows = AtomicUsize::new(0);
2394 let rows_data: Vec<Vec<Option<f64>>> = (0..out.rows as isize)
2395 .into_par_iter()
2396 .map(|row| {
2397 let mut row_values = vec![None; out.cols * out.bands];
2398 let y = out_y_max - (row as f64 + 0.5) * out.cell_size_y;
2399
2400 let mut batch_coords: Vec<(f64, f64)> = Vec::with_capacity(out.cols);
2404 let mut batch_cols: Vec<usize> = Vec::with_capacity(out.cols);
2405 for col in 0..out.cols as isize {
2406 let x = out.x_min + (col as f64 + 0.5) * out.cell_size_x;
2407 if let Some(ring) = &footprint_ring {
2408 if !point_in_polygon(x, y, ring) {
2409 continue;
2410 }
2411 }
2412 batch_coords.push((x, y));
2413 batch_cols.push(col as usize);
2414 }
2415
2416 let errors = dst_crs.transform_to_batch(&mut batch_coords, src_crs);
2420
2421 for (i, &col) in batch_cols.iter().enumerate() {
2422 if errors[i].is_some() {
2423 continue;
2424 }
2425 let (sx, sy) = batch_coords[i];
2426 for band in 0..out.bands as isize {
2427 if let Some(v) = self.sample_world(
2428 band,
2429 sx,
2430 sy,
2431 options.resample,
2432 options.nodata_policy,
2433 ) {
2434 row_values[band as usize * out.cols + col] = Some(v);
2435 }
2436 }
2437 }
2438
2439 if let Some(progress_cb) = progress {
2440 let done = completed_rows.fetch_add(1, Ordering::Relaxed) + 1;
2441 progress_cb(done as f64 / total_rows as f64);
2442 }
2443
2444 row_values
2445 })
2446 .collect();
2447
2448 for (row, row_values) in rows_data.into_iter().enumerate() {
2449 let row = row as isize;
2450 for band in 0..out.bands {
2451 for col in 0..out.cols {
2452 if let Some(v) = row_values[band * out.cols + col] {
2453 out.set_unchecked(band as isize, row, col as isize, v);
2454 }
2455 }
2456 }
2457 }
2458
2459 if let Some(progress_cb) = progress {
2460 progress_cb(1.0);
2461 }
2462
2463 Ok(out)
2464 }
2465
2466 fn source_crs_for_reprojection(&self) -> Result<Crs> {
2467 if let Some(src_epsg) = self.crs.epsg {
2468 return Crs::from_epsg(src_epsg).map_err(|e| {
2469 RasterError::Other(format!("source EPSG {src_epsg} is not supported: {e}"))
2470 });
2471 }
2472
2473 if let Some(wkt) = self.crs.wkt.as_deref() {
2474 let trimmed = wkt.trim();
2475 if !trimmed.is_empty() {
2476 return wbprojection::from_wkt(trimmed).map_err(|e| {
2477 RasterError::Other(format!("source CRS WKT is not supported: {e}"))
2478 });
2479 }
2480 }
2481
2482 if let Some(proj) = self.crs.proj4.as_deref() {
2483 let trimmed = proj.trim();
2484 if !trimmed.is_empty() {
2485 return from_proj_string(trimmed).map_err(|e| {
2486 RasterError::Other(format!("source CRS PROJ string is not supported: {e}"))
2487 });
2488 }
2489 }
2490
2491 Err(RasterError::Other(
2492 "reproject_to_epsg requires source CRS metadata (EPSG, WKT, or PROJ string)"
2493 .to_string(),
2494 ))
2495 }
2496
2497 pub fn sample_world(
2499 &self,
2500 band: isize,
2501 x: f64,
2502 y: f64,
2503 method: ResampleMethod,
2504 nodata_policy: NodataPolicy,
2505 ) -> Option<f64> {
2506 let col_f = (x - self.x_min) / self.cell_size_x - 0.5;
2507 let row_f = (self.y_max() - y) / self.cell_size_y - 0.5;
2508 match method {
2509 ResampleMethod::Nearest => self.sample_nearest_pixel(band, col_f, row_f),
2510 ResampleMethod::Bilinear => match nodata_policy {
2511 NodataPolicy::Strict => self.sample_bilinear_strict_pixel(band, col_f, row_f),
2512 NodataPolicy::PartialKernel => {
2513 self.sample_bilinear_partial_pixel(band, col_f, row_f)
2514 }
2515 NodataPolicy::Fill => self
2516 .sample_bilinear_strict_pixel(band, col_f, row_f)
2517 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
2518 },
2519 ResampleMethod::Cubic => match nodata_policy {
2520 NodataPolicy::Strict => self.sample_cubic_strict_pixel(band, col_f, row_f),
2521 NodataPolicy::PartialKernel => self.sample_cubic_partial_pixel(band, col_f, row_f),
2522 NodataPolicy::Fill => self
2523 .sample_cubic_strict_pixel(band, col_f, row_f)
2524 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
2525 },
2526 ResampleMethod::Lanczos => match nodata_policy {
2527 NodataPolicy::Strict => self.sample_lanczos_strict_pixel(band, col_f, row_f),
2528 NodataPolicy::PartialKernel => {
2529 self.sample_lanczos_partial_pixel(band, col_f, row_f)
2530 }
2531 NodataPolicy::Fill => self
2532 .sample_lanczos_strict_pixel(band, col_f, row_f)
2533 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
2534 },
2535 ResampleMethod::Average => self.sample_window_stat_pixel(
2536 band,
2537 col_f,
2538 row_f,
2539 WindowStat::Mean,
2540 nodata_policy,
2541 ),
2542 ResampleMethod::Min => self.sample_window_stat_pixel(
2543 band,
2544 col_f,
2545 row_f,
2546 WindowStat::Min,
2547 nodata_policy,
2548 ),
2549 ResampleMethod::Max => self.sample_window_stat_pixel(
2550 band,
2551 col_f,
2552 row_f,
2553 WindowStat::Max,
2554 nodata_policy,
2555 ),
2556 ResampleMethod::Mode => self.sample_window_stat_pixel(
2557 band,
2558 col_f,
2559 row_f,
2560 WindowStat::Mode,
2561 nodata_policy,
2562 ),
2563 ResampleMethod::Median => self.sample_window_stat_pixel(
2564 band,
2565 col_f,
2566 row_f,
2567 WindowStat::Median,
2568 nodata_policy,
2569 ),
2570 ResampleMethod::StdDev => self.sample_window_stat_pixel(
2571 band,
2572 col_f,
2573 row_f,
2574 WindowStat::StdDev,
2575 nodata_policy,
2576 ),
2577 }
2578 }
2579
2580 fn sample_window_stat_pixel(
2581 &self,
2582 band: isize,
2583 col_f: f64,
2584 row_f: f64,
2585 stat: WindowStat,
2586 nodata_policy: NodataPolicy,
2587 ) -> Option<f64> {
2588 if !col_f.is_finite() || !row_f.is_finite() {
2589 return None;
2590 }
2591
2592 let center_col = col_f.round() as isize;
2593 let center_row = row_f.round() as isize;
2594 let mut values = Vec::with_capacity(9);
2595 let mut valid_count = 0usize;
2596
2597 for dy in -1..=1 {
2598 for dx in -1..=1 {
2599 let c = center_col + dx;
2600 let r = center_row + dy;
2601 if c < 0 || r < 0 || c >= self.cols as isize || r >= self.rows as isize {
2602 continue;
2603 }
2604 if let Some(v) = self.get_opt(band, r, c) {
2605 values.push(v);
2606 valid_count += 1;
2607 }
2608 }
2609 }
2610
2611 match nodata_policy {
2612 NodataPolicy::Strict if valid_count < 9 => None,
2613 NodataPolicy::PartialKernel | NodataPolicy::Strict => {
2614 reduce_window_values(&values, stat)
2615 }
2616 NodataPolicy::Fill => reduce_window_values(&values, stat)
2617 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
2618 }
2619 }
2620
2621 fn sample_nearest_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2622 if !col_f.is_finite() || !row_f.is_finite() {
2623 return None;
2624 }
2625 let col = col_f.round() as isize;
2626 let row = row_f.round() as isize;
2627 self.get_opt(band, row, col)
2628 }
2629
2630 fn sample_bilinear_strict_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2631 if !col_f.is_finite() || !row_f.is_finite() {
2632 return None;
2633 }
2634 let c0 = col_f.floor() as isize;
2635 let r0 = row_f.floor() as isize;
2636 let c1 = c0 + 1;
2637 let r1 = r0 + 1;
2638 if c0 < 0 || r0 < 0 || c1 >= self.cols as isize || r1 >= self.rows as isize {
2639 return None;
2640 }
2641
2642 let tx = col_f - c0 as f64;
2643 let ty = row_f - r0 as f64;
2644
2645 if let Some(values) = self.data_f64() {
2646 return self.sample_bilinear_strict_simd_f64(values, band, r0, c0, tx, ty);
2647 }
2648 if let Some(values) = self.data_f32() {
2649 return self.sample_bilinear_strict_simd_f32(values, band, r0, c0, tx, ty);
2650 }
2651
2652 let q00 = self.get_opt(band, r0, c0)?;
2653 let q10 = self.get_opt(band, r0, c1)?;
2654 let q01 = self.get_opt(band, r1, c0)?;
2655 let q11 = self.get_opt(band, r1, c1)?;
2656
2657 let a = q00 * (1.0 - tx) + q10 * tx;
2658 let b = q01 * (1.0 - tx) + q11 * tx;
2659 Some(a * (1.0 - ty) + b * ty)
2660 }
2661
2662 #[inline]
2663 fn sample_bilinear_strict_simd_f64(
2664 &self,
2665 values: &[f64],
2666 band: isize,
2667 r0: isize,
2668 c0: isize,
2669 tx: f64,
2670 ty: f64,
2671 ) -> Option<f64> {
2672 let band_stride = self.rows * self.cols;
2673 let base = band as usize * band_stride + r0 as usize * self.cols + c0 as usize;
2674 let q00 = values[base];
2675 let q10 = values[base + 1];
2676 let q01 = values[base + self.cols];
2677 let q11 = values[base + self.cols + 1];
2678
2679 if self.is_nodata(q00)
2680 || self.is_nodata(q10)
2681 || self.is_nodata(q01)
2682 || self.is_nodata(q11)
2683 {
2684 return None;
2685 }
2686
2687 let weights = f64x4::new([
2688 (1.0 - tx) * (1.0 - ty),
2689 tx * (1.0 - ty),
2690 (1.0 - tx) * ty,
2691 tx * ty,
2692 ]);
2693 let vals = f64x4::new([q00, q10, q01, q11]);
2694 let weighted = <[f64; 4]>::from(vals * weights);
2695 Some(weighted.into_iter().sum())
2696 }
2697
2698 #[inline]
2699 fn sample_bilinear_strict_simd_f32(
2700 &self,
2701 values: &[f32],
2702 band: isize,
2703 r0: isize,
2704 c0: isize,
2705 tx: f64,
2706 ty: f64,
2707 ) -> Option<f64> {
2708 let band_stride = self.rows * self.cols;
2709 let base = band as usize * band_stride + r0 as usize * self.cols + c0 as usize;
2710 let q00 = values[base] as f64;
2711 let q10 = values[base + 1] as f64;
2712 let q01 = values[base + self.cols] as f64;
2713 let q11 = values[base + self.cols + 1] as f64;
2714
2715 if self.is_nodata(q00)
2716 || self.is_nodata(q10)
2717 || self.is_nodata(q01)
2718 || self.is_nodata(q11)
2719 {
2720 return None;
2721 }
2722
2723 let weights = f64x4::new([
2724 (1.0 - tx) * (1.0 - ty),
2725 tx * (1.0 - ty),
2726 (1.0 - tx) * ty,
2727 tx * ty,
2728 ]);
2729 let vals = f64x4::new([q00, q10, q01, q11]);
2730 let weighted = <[f64; 4]>::from(vals * weights);
2731 Some(weighted.into_iter().sum())
2732 }
2733
2734 fn sample_bilinear_partial_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2735 if !col_f.is_finite() || !row_f.is_finite() {
2736 return None;
2737 }
2738 let c0 = col_f.floor() as isize;
2739 let r0 = row_f.floor() as isize;
2740 let c1 = c0 + 1;
2741 let r1 = r0 + 1;
2742
2743 let tx = col_f - c0 as f64;
2744 let ty = row_f - r0 as f64;
2745
2746 let neighbors = [
2747 (c0, r0, (1.0 - tx) * (1.0 - ty)),
2748 (c1, r0, tx * (1.0 - ty)),
2749 (c0, r1, (1.0 - tx) * ty),
2750 (c1, r1, tx * ty),
2751 ];
2752
2753 let mut sum = 0.0;
2754 let mut wsum = 0.0;
2755 for (c, r, w) in neighbors {
2756 if w <= 0.0 || c < 0 || r < 0 || c >= self.cols as isize || r >= self.rows as isize {
2757 continue;
2758 }
2759 if let Some(v) = self.get_opt(band, r, c) {
2760 sum += v * w;
2761 wsum += w;
2762 }
2763 }
2764
2765 if wsum > 0.0 {
2766 Some(sum / wsum)
2767 } else {
2768 None
2769 }
2770 }
2771
2772 fn sample_cubic_strict_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2773 if !col_f.is_finite() || !row_f.is_finite() {
2774 return None;
2775 }
2776 let c1 = col_f.floor() as isize;
2777 let r1 = row_f.floor() as isize;
2778 if c1 - 1 < 0
2779 || r1 - 1 < 0
2780 || c1 + 2 >= self.cols as isize
2781 || r1 + 2 >= self.rows as isize
2782 {
2783 return None;
2784 }
2785
2786 let tx = col_f - c1 as f64;
2787 let ty = row_f - r1 as f64;
2788 let wx = cubic_bspline_weights(tx);
2789 let wy = cubic_bspline_weights(ty);
2790
2791 self.sample_cubic_simd_kernel(&wx, &wy, band, c1 - 1, r1 - 1)
2793 }
2794
2795 #[inline]
2798 fn sample_cubic_simd_kernel(
2799 &self,
2800 wx: &[f64; 4],
2801 wy: &[f64; 4],
2802 band: isize,
2803 c_start: isize,
2804 r_start: isize,
2805 ) -> Option<f64> {
2806 let mut row_sums = [0.0_f64; 4];
2809
2810 for (j, _wyj) in wy.iter().enumerate() {
2811 let rr = r_start + j as isize;
2812 let mut row_sum = 0.0;
2813
2814 for (i, wxi) in wx.iter().enumerate() {
2816 let cc = c_start + i as isize;
2817 let v = self.get_opt(band, rr, cc)?;
2818 row_sum += v * *wxi;
2819 }
2820
2821 row_sums[j] = row_sum;
2822 }
2823
2824 let mut sum = 0.0;
2826 for (j, wyj) in wy.iter().enumerate() {
2827 sum += row_sums[j] * *wyj;
2828 }
2829
2830 Some(sum)
2831 }
2832
2833 fn sample_cubic_partial_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2834 if !col_f.is_finite() || !row_f.is_finite() {
2835 return None;
2836 }
2837 let c1 = col_f.floor() as isize;
2838 let r1 = row_f.floor() as isize;
2839 let tx = col_f - c1 as f64;
2840 let ty = row_f - r1 as f64;
2841 let wx = cubic_bspline_weights(tx);
2842 let wy = cubic_bspline_weights(ty);
2843
2844 let mut sum = 0.0;
2845 let mut wsum = 0.0;
2846
2847 for (j, wyj) in wy.iter().enumerate() {
2848 let rr = clamp_isize(r1 + j as isize - 1, 0, self.rows as isize - 1);
2849 if *wyj <= 0.0 {
2850 continue;
2851 }
2852 for (i, wxi) in wx.iter().enumerate() {
2853 let cc = clamp_isize(c1 + i as isize - 1, 0, self.cols as isize - 1);
2854 let w = *wxi * *wyj;
2855 if w <= 0.0 {
2856 continue;
2857 }
2858 if let Some(v) = self.get_opt(band, rr, cc) {
2859 sum += v * w;
2860 wsum += w;
2861 }
2862 }
2863 }
2864
2865 if wsum > 0.0 {
2866 Some(sum / wsum)
2867 } else {
2868 None
2869 }
2870 }
2871
2872 fn sample_lanczos_strict_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2873 if !col_f.is_finite() || !row_f.is_finite() {
2874 return None;
2875 }
2876
2877 let c0 = col_f.floor() as isize;
2878 let r0 = row_f.floor() as isize;
2879 if c0 - 2 < 0
2880 || r0 - 2 < 0
2881 || c0 + 3 >= self.cols as isize
2882 || r0 + 3 >= self.rows as isize
2883 {
2884 return None;
2885 }
2886
2887 let wx = lanczos3_weights(col_f, c0);
2888 let wy = lanczos3_weights(row_f, r0);
2889
2890 if let Some(values) = self.data_f64() {
2891 return self.sample_lanczos_strict_simd_f64(values, band, c0, r0, &wx, &wy);
2892 }
2893 if let Some(values) = self.data_f32() {
2894 return self.sample_lanczos_strict_simd_f32(values, band, c0, r0, &wx, &wy);
2895 }
2896
2897 let mut sum = 0.0;
2898 let mut wsum = 0.0;
2899 for (j, wyj) in wy.iter().enumerate() {
2900 let rr = r0 + j as isize - 2;
2901 for (i, wxi) in wx.iter().enumerate() {
2902 let cc = c0 + i as isize - 2;
2903 let v = self.get_opt(band, rr, cc)?;
2904 let w = *wxi * *wyj;
2905 sum += v * w;
2906 wsum += w;
2907 }
2908 }
2909
2910 if wsum.abs() > 1e-12 {
2911 Some(sum / wsum)
2912 } else {
2913 None
2914 }
2915 }
2916
2917 #[inline]
2918 fn sample_lanczos_strict_simd_f64(
2919 &self,
2920 values: &[f64],
2921 band: isize,
2922 c0: isize,
2923 r0: isize,
2924 wx: &[f64; 6],
2925 wy: &[f64; 6],
2926 ) -> Option<f64> {
2927 let band_stride = self.rows * self.cols;
2928 let base = band as usize * band_stride;
2929
2930 let wx0 = f64x4::new([wx[0], wx[1], wx[2], wx[3]]);
2931 let wx1 = f64x4::new([wx[4], wx[5], 0.0, 0.0]);
2932 let wx_sum: f64 = wx.iter().copied().sum();
2933
2934 let mut sum = 0.0;
2935 let mut wsum = 0.0;
2936 for (j, wyj) in wy.iter().enumerate() {
2937 let rr = (r0 + j as isize - 2) as usize;
2938 let cc = (c0 - 2) as usize;
2939 let row_offset = base + rr * self.cols + cc;
2940
2941 let v = [
2942 values[row_offset],
2943 values[row_offset + 1],
2944 values[row_offset + 2],
2945 values[row_offset + 3],
2946 values[row_offset + 4],
2947 values[row_offset + 5],
2948 ];
2949 if v.into_iter().any(|cell| self.is_nodata(cell)) {
2950 return None;
2951 }
2952
2953 let v0 = f64x4::new([v[0], v[1], v[2], v[3]]);
2954 let v1 = f64x4::new([v[4], v[5], 0.0, 0.0]);
2955 let d0 = <[f64; 4]>::from(v0 * wx0);
2956 let d1 = <[f64; 4]>::from(v1 * wx1);
2957 let row_sum = d0.into_iter().sum::<f64>() + d1.into_iter().sum::<f64>();
2958 sum += row_sum * *wyj;
2959 wsum += wx_sum * *wyj;
2960 }
2961
2962 if wsum.abs() > 1e-12 {
2963 Some(sum / wsum)
2964 } else {
2965 None
2966 }
2967 }
2968
2969 #[inline]
2970 fn sample_lanczos_strict_simd_f32(
2971 &self,
2972 values: &[f32],
2973 band: isize,
2974 c0: isize,
2975 r0: isize,
2976 wx: &[f64; 6],
2977 wy: &[f64; 6],
2978 ) -> Option<f64> {
2979 let band_stride = self.rows * self.cols;
2980 let base = band as usize * band_stride;
2981
2982 let wx0 = f64x4::new([wx[0], wx[1], wx[2], wx[3]]);
2983 let wx1 = f64x4::new([wx[4], wx[5], 0.0, 0.0]);
2984 let wx_sum: f64 = wx.iter().copied().sum();
2985
2986 let mut sum = 0.0;
2987 let mut wsum = 0.0;
2988 for (j, wyj) in wy.iter().enumerate() {
2989 let rr = (r0 + j as isize - 2) as usize;
2990 let cc = (c0 - 2) as usize;
2991 let row_offset = base + rr * self.cols + cc;
2992
2993 let v = [
2994 values[row_offset] as f64,
2995 values[row_offset + 1] as f64,
2996 values[row_offset + 2] as f64,
2997 values[row_offset + 3] as f64,
2998 values[row_offset + 4] as f64,
2999 values[row_offset + 5] as f64,
3000 ];
3001 if v.into_iter().any(|cell| self.is_nodata(cell)) {
3002 return None;
3003 }
3004
3005 let v0 = f64x4::new([v[0], v[1], v[2], v[3]]);
3006 let v1 = f64x4::new([v[4], v[5], 0.0, 0.0]);
3007 let d0 = <[f64; 4]>::from(v0 * wx0);
3008 let d1 = <[f64; 4]>::from(v1 * wx1);
3009 let row_sum = d0.into_iter().sum::<f64>() + d1.into_iter().sum::<f64>();
3010 sum += row_sum * *wyj;
3011 wsum += wx_sum * *wyj;
3012 }
3013
3014 if wsum.abs() > 1e-12 {
3015 Some(sum / wsum)
3016 } else {
3017 None
3018 }
3019 }
3020
3021 fn sample_lanczos_partial_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
3022 if !col_f.is_finite() || !row_f.is_finite() {
3023 return None;
3024 }
3025
3026 let c0 = col_f.floor() as isize;
3027 let r0 = row_f.floor() as isize;
3028 let wx = lanczos3_weights(col_f, c0);
3029 let wy = lanczos3_weights(row_f, r0);
3030
3031 let mut sum = 0.0;
3032 let mut wsum = 0.0;
3033
3034 for (j, wyj) in wy.iter().enumerate() {
3035 let rr = clamp_isize(r0 + j as isize - 2, 0, self.rows as isize - 1);
3036 for (i, wxi) in wx.iter().enumerate() {
3037 let cc = clamp_isize(c0 + i as isize - 2, 0, self.cols as isize - 1);
3038 let w = *wxi * *wyj;
3039 if w == 0.0 {
3040 continue;
3041 }
3042 if let Some(v) = self.get_opt(band, rr, cc) {
3043 sum += v * w;
3044 wsum += w;
3045 }
3046 }
3047 }
3048
3049 if wsum.abs() > 1e-12 {
3050 Some(sum / wsum)
3051 } else {
3052 None
3053 }
3054 }
3055
3056 fn stats_accumulator_range_with_mode(
3059 &self,
3060 start: usize,
3061 end: usize,
3062 mode: StatisticsComputationMode,
3063 ) -> StatsAccumulator {
3064 match mode {
3065 StatisticsComputationMode::Auto | StatisticsComputationMode::Simd => {
3066 self.stats_accumulator_range_simd(start, end)
3067 }
3068 StatisticsComputationMode::Scalar => self.stats_accumulator_range_scalar(start, end),
3069 }
3070 }
3071
3072 fn stats_accumulator_range_scalar(&self, start: usize, end: usize) -> StatsAccumulator {
3073 let mut accumulator = StatsAccumulator::default();
3074
3075 for idx in start..end {
3076 let value = self.data.get_f64(idx);
3077 if self.is_nodata(value) {
3078 accumulator.nodata_count += 1;
3079 } else {
3080 accumulator.min = accumulator.min.min(value);
3081 accumulator.max = accumulator.max.max(value);
3082 accumulator.sum += value;
3083 accumulator.sum_sq += value * value;
3084 accumulator.valid_count += 1;
3085 }
3086 }
3087
3088 accumulator
3089 }
3090
3091 fn stats_accumulator_range_simd(&self, start: usize, end: usize) -> StatsAccumulator {
3095 if let Some(values) = self.data_f64() {
3096 return self.stats_accumulator_range_simd_f64(values, start, end);
3097 }
3098 if let Some(values) = self.data_f32() {
3099 return self.stats_accumulator_range_simd_f32(values, start, end);
3100 }
3101
3102 self.stats_accumulator_range_scalar(start, end)
3103 }
3104
3105 fn stats_accumulator_range_simd_f64(
3106 &self,
3107 values: &[f64],
3108 start: usize,
3109 end: usize,
3110 ) -> StatsAccumulator {
3111 let mut accumulator = StatsAccumulator::default();
3112 let nd = self.nodata;
3113
3114 let simd_end = start + ((end - start) / 4) * 4;
3115 let mut simd_min = f64x4::splat(f64::INFINITY);
3116 let mut simd_max = f64x4::splat(f64::NEG_INFINITY);
3117 let mut simd_sum = f64x4::splat(0.0);
3118 let mut simd_sum_sq = f64x4::splat(0.0);
3119 let zero_v = f64x4::splat(0.0);
3120 let nd_v = f64x4::splat(nd);
3121 let inf_v = f64x4::splat(f64::INFINITY);
3122 let neg_inf_v = f64x4::splat(f64::NEG_INFINITY);
3123
3124 let mut idx = start;
3125 while idx < simd_end {
3126 let chunk = &values[idx..idx + 4];
3127 let values = f64x4::new([chunk[0], chunk[1], chunk[2], chunk[3]]);
3128
3129 let not_nodata = values.simd_ne(nd_v);
3130
3131 let values_for_min = not_nodata.blend(values, inf_v);
3132 let values_for_max = not_nodata.blend(values, neg_inf_v);
3133 simd_min = simd_min.min(values_for_min);
3134 simd_max = simd_max.max(values_for_max);
3135
3136 let masked_values = not_nodata.blend(values, zero_v);
3137 simd_sum = simd_sum + masked_values;
3138 simd_sum_sq = simd_sum_sq + masked_values * masked_values;
3139
3140 for &val in chunk {
3141 if val == nd {
3142 accumulator.nodata_count += 1;
3143 } else {
3144 accumulator.valid_count += 1;
3145 }
3146 }
3147
3148 idx += 4;
3149 }
3150
3151 let sum_arr = <[f64; 4]>::from(simd_sum);
3152 let sum_sq_arr = <[f64; 4]>::from(simd_sum_sq);
3153 let min_arr = <[f64; 4]>::from(simd_min);
3154 let max_arr = <[f64; 4]>::from(simd_max);
3155
3156 for i in 0..4 {
3157 accumulator.sum += sum_arr[i];
3158 accumulator.sum_sq += sum_sq_arr[i];
3159 accumulator.min = accumulator.min.min(min_arr[i]);
3160 accumulator.max = accumulator.max.max(max_arr[i]);
3161 }
3162
3163 for &value in &values[simd_end..end] {
3164 if value == nd {
3165 accumulator.nodata_count += 1;
3166 } else {
3167 accumulator.min = accumulator.min.min(value);
3168 accumulator.max = accumulator.max.max(value);
3169 accumulator.sum += value;
3170 accumulator.sum_sq += value * value;
3171 accumulator.valid_count += 1;
3172 }
3173 }
3174
3175 accumulator
3176 }
3177
3178 fn stats_accumulator_range_simd_f32(
3179 &self,
3180 values: &[f32],
3181 start: usize,
3182 end: usize,
3183 ) -> StatsAccumulator {
3184 let mut accumulator = StatsAccumulator::default();
3185 let nd = self.nodata as f32;
3186
3187 let simd_end = start + ((end - start) / 4) * 4;
3188 let mut simd_min = f64x4::splat(f64::INFINITY);
3189 let mut simd_max = f64x4::splat(f64::NEG_INFINITY);
3190 let mut simd_sum = f64x4::splat(0.0);
3191 let mut simd_sum_sq = f64x4::splat(0.0);
3192 let zero_v = f64x4::splat(0.0);
3193 let nd_v = f64x4::splat(nd as f64);
3194 let inf_v = f64x4::splat(f64::INFINITY);
3195 let neg_inf_v = f64x4::splat(f64::NEG_INFINITY);
3196
3197 let mut idx = start;
3198 while idx < simd_end {
3199 let chunk = &values[idx..idx + 4];
3200 let values = f64x4::new([
3201 chunk[0] as f64,
3202 chunk[1] as f64,
3203 chunk[2] as f64,
3204 chunk[3] as f64,
3205 ]);
3206
3207 let not_nodata = values.simd_ne(nd_v);
3208 let values_for_min = not_nodata.blend(values, inf_v);
3209 let values_for_max = not_nodata.blend(values, neg_inf_v);
3210 simd_min = simd_min.min(values_for_min);
3211 simd_max = simd_max.max(values_for_max);
3212
3213 let masked_values = not_nodata.blend(values, zero_v);
3214 simd_sum = simd_sum + masked_values;
3215 simd_sum_sq = simd_sum_sq + masked_values * masked_values;
3216
3217 for &val in chunk {
3218 if val == nd {
3219 accumulator.nodata_count += 1;
3220 } else {
3221 accumulator.valid_count += 1;
3222 }
3223 }
3224
3225 idx += 4;
3226 }
3227
3228 let sum_arr = <[f64; 4]>::from(simd_sum);
3229 let sum_sq_arr = <[f64; 4]>::from(simd_sum_sq);
3230 let min_arr = <[f64; 4]>::from(simd_min);
3231 let max_arr = <[f64; 4]>::from(simd_max);
3232
3233 for i in 0..4 {
3234 accumulator.sum += sum_arr[i];
3235 accumulator.sum_sq += sum_sq_arr[i];
3236 accumulator.min = accumulator.min.min(min_arr[i]);
3237 accumulator.max = accumulator.max.max(max_arr[i]);
3238 }
3239
3240 for &value in &values[simd_end..end] {
3241 if value == nd {
3242 accumulator.nodata_count += 1;
3243 } else {
3244 let value = value as f64;
3245 accumulator.min = accumulator.min.min(value);
3246 accumulator.max = accumulator.max.max(value);
3247 accumulator.sum += value;
3248 accumulator.sum_sq += value * value;
3249 accumulator.valid_count += 1;
3250 }
3251 }
3252
3253 accumulator
3254 }
3255
3256 fn statistics_for_index_range_with_mode(
3257 &self,
3258 start: usize,
3259 end: usize,
3260 mode: StatisticsComputationMode,
3261 ) -> Statistics {
3262 const PARALLEL_THRESHOLD: usize = 65_536;
3263 const CHUNK_SIZE: usize = 16_384;
3264
3265 let span = end.saturating_sub(start);
3266 if span == 0 {
3267 return Statistics {
3268 min: 0.0,
3269 max: 0.0,
3270 mean: 0.0,
3271 std_dev: 0.0,
3272 valid_count: 0,
3273 nodata_count: 0,
3274 };
3275 }
3276
3277 let total = if span < PARALLEL_THRESHOLD {
3278 self.stats_accumulator_range_with_mode(start, end, mode)
3279 } else {
3280 let chunk_starts: Vec<usize> = (start..end).step_by(CHUNK_SIZE).collect();
3281 let partials: Vec<StatsAccumulator> = chunk_starts
3282 .into_par_iter()
3283 .map(|chunk_start| {
3284 let chunk_end = (chunk_start + CHUNK_SIZE).min(end);
3285 self.stats_accumulator_range_with_mode(chunk_start, chunk_end, mode)
3286 })
3287 .collect();
3288
3289 partials.into_iter().fold(StatsAccumulator::default(), |mut lhs, rhs| {
3290 lhs.merge(rhs);
3291 lhs
3292 })
3293 };
3294
3295 total.to_statistics()
3296 }
3297
3298 pub fn statistics(&self) -> Statistics {
3300 self.statistics_with_mode(StatisticsComputationMode::Auto)
3301 }
3302
3303 pub fn statistics_with_mode(&self, mode: StatisticsComputationMode) -> Statistics {
3305 self.statistics_for_index_range_with_mode(0, self.data.len(), mode)
3306 }
3307
3308 pub fn statistics_band(&self, band: isize) -> Result<Statistics> {
3310 self.statistics_band_with_mode(band, StatisticsComputationMode::Auto)
3311 }
3312
3313 pub fn statistics_band_with_mode(
3315 &self,
3316 band: isize,
3317 mode: StatisticsComputationMode,
3318 ) -> Result<Statistics> {
3319 if band < 0 || band >= self.bands as isize {
3320 return Err(RasterError::OutOfBounds {
3321 band,
3322 col: 0,
3323 row: 0,
3324 bands: self.bands,
3325 cols: self.cols,
3326 rows: self.rows,
3327 });
3328 }
3329
3330 let band = band as usize;
3331 let band_stride = self.rows * self.cols;
3332 let start = band * band_stride;
3333 let end = start + band_stride;
3334
3335 Ok(self.statistics_for_index_range_with_mode(start, end, mode))
3336 }
3337
3338 #[inline]
3342 pub fn band_slice(&self, band: isize) -> Vec<f64> {
3343 if band < 0 || band >= self.bands as isize {
3344 return Vec::new();
3345 }
3346 let band_stride = self.rows * self.cols;
3347 let start = band as usize * band_stride;
3348 (start..start + band_stride)
3349 .map(|i| self.data.get_f64(i))
3350 .collect()
3351 }
3352
3353 pub fn set_band_slice(&mut self, band: isize, values: &[f64]) -> Result<()> {
3355 let expected = self.rows * self.cols;
3356 if values.len() != expected {
3357 return Err(RasterError::InvalidDimensions {
3358 cols: self.cols,
3359 rows: values.len(),
3360 });
3361 }
3362 if band < 0 || band >= self.bands as isize {
3363 return Err(RasterError::OutOfBounds {
3364 band,
3365 col: 0,
3366 row: 0,
3367 bands: self.bands,
3368 cols: self.cols,
3369 rows: self.rows,
3370 });
3371 }
3372 let band_stride = self.rows * self.cols;
3373 let start = band as usize * band_stride;
3374 for (i, v) in values.iter().copied().enumerate() {
3375 self.data.set_f64(start + i, v);
3376 }
3377 Ok(())
3378 }
3379
3380 #[inline]
3382 pub fn row_slice(&self, band: isize, row: isize) -> Vec<f64> {
3383 if band < 0 || band >= self.bands as isize || row < 0 || row >= self.rows as isize {
3384 return Vec::new();
3385 }
3386 let band_stride = self.rows * self.cols;
3387 let start = band as usize * band_stride + row as usize * self.cols;
3388 (start..start + self.cols).map(|i| self.data.get_f64(i)).collect()
3389 }
3390
3391 pub fn set_row_slice(&mut self, band: isize, row: isize, values: &[f64]) -> Result<()> {
3393 if values.len() != self.cols {
3394 return Err(RasterError::InvalidDimensions { cols: values.len(), rows: self.rows });
3395 }
3396 if band < 0 || band >= self.bands as isize || row < 0 || row >= self.rows as isize {
3397 return Err(RasterError::OutOfBounds {
3398 band,
3399 col: 0,
3400 row,
3401 bands: self.bands,
3402 cols: self.cols,
3403 rows: self.rows,
3404 });
3405 }
3406 let band_stride = self.rows * self.cols;
3407 let start = band as usize * band_stride + row as usize * self.cols;
3408 for (i, v) in values.iter().copied().enumerate() {
3409 self.data.set_f64(start + i, v);
3410 }
3411 Ok(())
3412 }
3413
3414 pub fn iter_valid(&self) -> impl Iterator<Item = (isize, isize, isize, f64)> + '_ {
3416 self.data.iter_f64().enumerate().filter_map(move |(idx, v)| {
3417 if self.is_nodata(v) {
3418 None
3419 } else {
3420 let band_stride = self.rows * self.cols;
3421 let band = idx / band_stride;
3422 let rem = idx % band_stride;
3423 let row = rem / self.cols;
3424 let col = rem % self.cols;
3425 Some((band as isize, row as isize, col as isize, v))
3426 }
3427 })
3428 }
3429
3430 pub fn iter_valid_band(
3432 &self,
3433 band: isize,
3434 ) -> Result<Box<dyn Iterator<Item = (isize, isize, f64)> + '_>> {
3435 if band < 0 || band >= self.bands as isize {
3436 return Err(RasterError::OutOfBounds {
3437 band,
3438 col: 0,
3439 row: 0,
3440 bands: self.bands,
3441 cols: self.cols,
3442 rows: self.rows,
3443 });
3444 }
3445
3446 let b = band as usize;
3447 let band_stride = self.rows * self.cols;
3448 let start = b * band_stride;
3449 Ok(Box::new((0..band_stride).filter_map(move |i| {
3450 let v = self.data.get_f64(start + i);
3451 if self.is_nodata(v) {
3452 None
3453 } else {
3454 let row = i / self.cols;
3455 let col = i % self.cols;
3456 Some((row as isize, col as isize, v))
3457 }
3458 })))
3459 }
3460
3461 pub fn iter_band_rows(&self, band: isize) -> Result<Box<dyn Iterator<Item = Vec<f64>> + '_>> {
3463 if band < 0 || band >= self.bands as isize {
3464 return Err(RasterError::OutOfBounds {
3465 band,
3466 col: 0,
3467 row: 0,
3468 bands: self.bands,
3469 cols: self.cols,
3470 rows: self.rows,
3471 });
3472 }
3473
3474 Ok(Box::new((0..self.rows).map(move |row| self.row_slice(band, row as isize))))
3475 }
3476
3477 pub fn for_each_band_row_mut<F>(&mut self, band: isize, mut f: F) -> Result<()>
3482 where
3483 F: FnMut(isize, RasterRowMut<'_>),
3484 {
3485 if band < 0 || band >= self.bands as isize {
3486 return Err(RasterError::OutOfBounds {
3487 band,
3488 col: 0,
3489 row: 0,
3490 bands: self.bands,
3491 cols: self.cols,
3492 rows: self.rows,
3493 });
3494 }
3495
3496 let b = band as usize;
3497 let band_stride = self.rows * self.cols;
3498 let start = b * band_stride;
3499 let end = start + band_stride;
3500
3501 match &mut self.data {
3502 RasterData::U8(v) => {
3503 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3504 f(row as isize, RasterRowMut::U8(chunk));
3505 }
3506 }
3507 RasterData::I8(v) => {
3508 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3509 f(row as isize, RasterRowMut::I8(chunk));
3510 }
3511 }
3512 RasterData::U16(v) => {
3513 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3514 f(row as isize, RasterRowMut::U16(chunk));
3515 }
3516 }
3517 RasterData::I16(v) => {
3518 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3519 f(row as isize, RasterRowMut::I16(chunk));
3520 }
3521 }
3522 RasterData::U32(v) => {
3523 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3524 f(row as isize, RasterRowMut::U32(chunk));
3525 }
3526 }
3527 RasterData::I32(v) => {
3528 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3529 f(row as isize, RasterRowMut::I32(chunk));
3530 }
3531 }
3532 RasterData::U64(v) => {
3533 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3534 f(row as isize, RasterRowMut::U64(chunk));
3535 }
3536 }
3537 RasterData::I64(v) => {
3538 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3539 f(row as isize, RasterRowMut::I64(chunk));
3540 }
3541 }
3542 RasterData::F32(v) => {
3543 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3544 f(row as isize, RasterRowMut::F32(chunk));
3545 }
3546 }
3547 RasterData::F64(v) => {
3548 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3549 f(row as isize, RasterRowMut::F64(chunk));
3550 }
3551 }
3552 }
3553
3554 Ok(())
3555 }
3556
3557 pub fn for_each_band_row<F>(&self, band: isize, mut f: F) -> Result<()>
3562 where
3563 F: FnMut(isize, RasterRowRef<'_>),
3564 {
3565 if band < 0 || band >= self.bands as isize {
3566 return Err(RasterError::OutOfBounds {
3567 band,
3568 col: 0,
3569 row: 0,
3570 bands: self.bands,
3571 cols: self.cols,
3572 rows: self.rows,
3573 });
3574 }
3575
3576 let b = band as usize;
3577 let band_stride = self.rows * self.cols;
3578 let start = b * band_stride;
3579 let end = start + band_stride;
3580
3581 match &self.data {
3582 RasterData::U8(v) => {
3583 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3584 f(row as isize, RasterRowRef::U8(chunk));
3585 }
3586 }
3587 RasterData::I8(v) => {
3588 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3589 f(row as isize, RasterRowRef::I8(chunk));
3590 }
3591 }
3592 RasterData::U16(v) => {
3593 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3594 f(row as isize, RasterRowRef::U16(chunk));
3595 }
3596 }
3597 RasterData::I16(v) => {
3598 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3599 f(row as isize, RasterRowRef::I16(chunk));
3600 }
3601 }
3602 RasterData::U32(v) => {
3603 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3604 f(row as isize, RasterRowRef::U32(chunk));
3605 }
3606 }
3607 RasterData::I32(v) => {
3608 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3609 f(row as isize, RasterRowRef::I32(chunk));
3610 }
3611 }
3612 RasterData::U64(v) => {
3613 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3614 f(row as isize, RasterRowRef::U64(chunk));
3615 }
3616 }
3617 RasterData::I64(v) => {
3618 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3619 f(row as isize, RasterRowRef::I64(chunk));
3620 }
3621 }
3622 RasterData::F32(v) => {
3623 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3624 f(row as isize, RasterRowRef::F32(chunk));
3625 }
3626 }
3627 RasterData::F64(v) => {
3628 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3629 f(row as isize, RasterRowRef::F64(chunk));
3630 }
3631 }
3632 }
3633
3634 Ok(())
3635 }
3636
3637 pub fn fill(&mut self, value: f64) {
3641 for i in 0..self.data.len() {
3642 self.data.set_f64(i, value);
3643 }
3644 }
3645
3646 pub fn fill_nodata(&mut self) {
3648 let nd = self.nodata;
3649 self.fill(nd);
3650 }
3651
3652 pub fn map_valid<F: Fn(f64) -> f64>(&mut self, f: F) {
3654 let nd = self.nodata;
3655 let nodata_nan = nd.is_nan();
3656 for i in 0..self.data.len() {
3657 let v = self.data.get_f64(i);
3658 let is_nd = if nodata_nan { v.is_nan() } else { (v - nd).abs() < 1e-10 * nd.abs().max(1.0) };
3659 if !is_nd {
3660 self.data.set_f64(i, f(v));
3661 }
3662 }
3663 }
3664
3665 pub fn map_valid_band<F: Fn(f64) -> f64>(&mut self, band: isize, f: F) -> Result<()> {
3667 if band < 0 || band >= self.bands as isize {
3668 return Err(RasterError::OutOfBounds {
3669 band,
3670 col: 0,
3671 row: 0,
3672 bands: self.bands,
3673 cols: self.cols,
3674 rows: self.rows,
3675 });
3676 }
3677
3678 let nd = self.nodata;
3679 let nodata_nan = nd.is_nan();
3680 let band_stride = self.rows * self.cols;
3681 let start = band as usize * band_stride;
3682 let end = start + band_stride;
3683
3684 for i in start..end {
3685 let v = self.data.get_f64(i);
3686 let is_nd = if nodata_nan {
3687 v.is_nan()
3688 } else {
3689 (v - nd).abs() < 1e-10 * nd.abs().max(1.0)
3690 };
3691 if !is_nd {
3692 self.data.set_f64(i, f(v));
3693 }
3694 }
3695 Ok(())
3696 }
3697
3698 pub fn replace(&mut self, from: f64, to: f64) {
3700 for i in 0..self.data.len() {
3701 let v = self.data.get_f64(i);
3702 if (v - from).abs() < f64::EPSILON {
3703 self.data.set_f64(i, to);
3704 }
3705 }
3706 }
3707
3708 pub fn replace_band(&mut self, band: isize, from: f64, to: f64) -> Result<()> {
3710 if band < 0 || band >= self.bands as isize {
3711 return Err(RasterError::OutOfBounds {
3712 band,
3713 col: 0,
3714 row: 0,
3715 bands: self.bands,
3716 cols: self.cols,
3717 rows: self.rows,
3718 });
3719 }
3720
3721 let band_stride = self.rows * self.cols;
3722 let start = band as usize * band_stride;
3723 let end = start + band_stride;
3724 for i in start..end {
3725 let v = self.data.get_f64(i);
3726 if (v - from).abs() < f64::EPSILON {
3727 self.data.set_f64(i, to);
3728 }
3729 }
3730 Ok(())
3731 }
3732
3733 pub fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
3737 let path = path.as_ref().to_string_lossy().to_string();
3738 let fmt = RasterFormat::detect(&path)?;
3739 fmt.read(&path)
3740 }
3741
3742 pub fn read_with_format<P: AsRef<Path>>(path: P, fmt: RasterFormat) -> Result<Self> {
3744 let path = path.as_ref().to_string_lossy().to_string();
3745 fmt.read(&path)
3746 }
3747
3748 pub fn write<P: AsRef<Path>>(&self, path: P, fmt: RasterFormat) -> Result<()> {
3750 let path = path.as_ref().to_string_lossy().to_string();
3751 fmt.write(self, &path)
3752 }
3753
3754 pub fn write_auto<P: AsRef<Path>>(&self, path: P) -> Result<()> {
3756 let path = path.as_ref().to_string_lossy().to_string();
3757 let fmt = RasterFormat::detect(&path)?;
3758 fmt.write(self, &path)
3759 }
3760
3761 pub fn write_geotiff_with_options<P: AsRef<Path>>(
3763 &self,
3764 path: P,
3765 opts: &crate::formats::geotiff::GeoTiffWriteOptions,
3766 ) -> Result<()> {
3767 let path = path.as_ref().to_string_lossy().to_string();
3768 crate::formats::geotiff::write_with_options(self, &path, opts)
3769 }
3770
3771 pub fn write_cog<P: AsRef<Path>>(&self, path: P) -> Result<()> {
3779 let opts = crate::formats::geotiff::GeoTiffWriteOptions {
3780 compression: Some(crate::formats::geotiff::GeoTiffCompression::Deflate),
3781 bigtiff: Some(false),
3782 layout: Some(crate::formats::geotiff::GeoTiffLayout::Cog { tile_size: 512 }),
3783 };
3784 self.write_geotiff_with_options(path, &opts)
3785 }
3786
3787 pub fn write_cog_with_tile_size<P: AsRef<Path>>(&self, path: P, tile_size: u32) -> Result<()> {
3795 let opts = crate::formats::geotiff::GeoTiffWriteOptions {
3796 compression: Some(crate::formats::geotiff::GeoTiffCompression::Deflate),
3797 bigtiff: Some(false),
3798 layout: Some(crate::formats::geotiff::GeoTiffLayout::Cog { tile_size }),
3799 };
3800 self.write_geotiff_with_options(path, &opts)
3801 }
3802
3803 pub fn write_cog_with_options<P: AsRef<Path>>(
3811 &self,
3812 path: P,
3813 opts: &crate::formats::geotiff::CogWriteOptions,
3814 ) -> Result<()> {
3815 let geotiff_opts = crate::formats::geotiff::GeoTiffWriteOptions {
3816 compression: Some(
3817 opts.compression
3818 .unwrap_or(crate::formats::geotiff::GeoTiffCompression::Deflate),
3819 ),
3820 bigtiff: Some(opts.bigtiff.unwrap_or(false)),
3821 layout: Some(crate::formats::geotiff::GeoTiffLayout::Cog {
3822 tile_size: opts.tile_size.unwrap_or(512),
3823 }),
3824 };
3825 self.write_geotiff_with_options(path, &geotiff_opts)
3826 }
3827
3828 pub fn write_jpeg2000_with_options<P: AsRef<Path>>(
3830 &self,
3831 path: P,
3832 opts: &crate::formats::jpeg2000::Jpeg2000WriteOptions,
3833 ) -> Result<()> {
3834 let path = path.as_ref().to_string_lossy().to_string();
3835 crate::formats::jpeg2000::write_with_options(self, &path, opts)
3836 }
3837}
3838
3839fn maybe_warn_area_of_use_mismatch(
3840 src: &Crs,
3841 dst: &Crs,
3842 src_extent: Extent,
3843 enabled: bool,
3844) {
3845 if !enabled {
3846 return;
3847 }
3848
3849 let src_area = src.area_of_use();
3850 let dst_area = dst.area_of_use();
3851 if src_area.is_none() && dst_area.is_none() {
3852 return;
3853 }
3854
3855 let Ok(wgs84) = Crs::from_epsg(4326) else {
3856 return;
3857 };
3858
3859 let sample_points = [
3860 (src_extent.x_min, src_extent.y_min),
3861 (src_extent.x_min, src_extent.y_max),
3862 (src_extent.x_max, src_extent.y_min),
3863 (src_extent.x_max, src_extent.y_max),
3864 (
3865 0.5 * (src_extent.x_min + src_extent.x_max),
3866 0.5 * (src_extent.y_min + src_extent.y_max),
3867 ),
3868 ];
3869
3870 let mut src_outside = 0usize;
3871 let mut dst_outside = 0usize;
3872 let mut checked = 0usize;
3873
3874 for (x, y) in sample_points {
3875 let Ok((lon, lat)) = src.transform_to(x, y, &wgs84) else {
3876 continue;
3877 };
3878 checked += 1;
3879
3880 if let Some(bb) = &src_area {
3881 if !bb.contains_geographic(lon, lat) {
3882 src_outside += 1;
3883 }
3884 }
3885 if let Some(bb) = &dst_area {
3886 if !bb.contains_geographic(lon, lat) {
3887 dst_outside += 1;
3888 }
3889 }
3890 }
3891
3892 if checked == 0 {
3893 return;
3894 }
3895
3896 if src_outside > 0 || dst_outside > 0 {
3897 eprintln!(
3898 "wbraster reprojection warning: sampled source extent appears outside CRS area of use (src outside: {src_outside}/{checked}, dst outside: {dst_outside}/{checked})"
3899 );
3900 }
3901}
3902
3903fn cubic_bspline_weights(t: f64) -> [f64; 4] {
3904 let t = t.clamp(0.0, 1.0);
3905 let t2 = t * t;
3906 let t3 = t2 * t;
3907 [
3908 ((1.0 - t) * (1.0 - t) * (1.0 - t)) / 6.0,
3909 (3.0 * t3 - 6.0 * t2 + 4.0) / 6.0,
3910 (-3.0 * t3 + 3.0 * t2 + 3.0 * t + 1.0) / 6.0,
3911 t3 / 6.0,
3912 ]
3913}
3914
3915fn lanczos_kernel(x: f64, a: f64) -> f64 {
3916 if x.abs() < 1e-12 {
3917 return 1.0;
3918 }
3919 if x.abs() >= a {
3920 return 0.0;
3921 }
3922 let pix = std::f64::consts::PI * x;
3923 let pix_over_a = pix / a;
3924 (pix.sin() / pix) * (pix_over_a.sin() / pix_over_a)
3925}
3926
3927fn lanczos3_weights(sample_f: f64, floor_idx: isize) -> [f64; 6] {
3928 let mut w = [0.0_f64; 6];
3929 for (i, wi) in w.iter_mut().enumerate() {
3930 let idx = floor_idx + i as isize - 2;
3931 let dx = sample_f - idx as f64;
3932 *wi = lanczos_kernel(dx, 3.0);
3933 }
3934 w
3935}
3936
3937fn sample_extent_boundary_points(extent: Extent, samples_per_edge: usize) -> Vec<(f64, f64)> {
3938 let n = samples_per_edge.max(1);
3939 let mut pts = Vec::with_capacity(4 * n);
3940
3941 for i in 0..=n {
3943 let t = i as f64 / n as f64;
3944 let x = extent.x_min + t * (extent.x_max - extent.x_min);
3945 pts.push((x, extent.y_min));
3946 pts.push((x, extent.y_max));
3947 }
3948
3949 for j in 1..n {
3951 let t = j as f64 / n as f64;
3952 let y = extent.y_min + t * (extent.y_max - extent.y_min);
3953 pts.push((extent.x_min, y));
3954 pts.push((extent.x_max, y));
3955 }
3956
3957 pts
3958}
3959
3960fn sample_extent_boundary_ring(extent: Extent, samples_per_edge: usize) -> Vec<(f64, f64)> {
3961 let n = samples_per_edge.max(1);
3962 let mut ring = Vec::with_capacity(n * 4);
3963
3964 for i in 0..n {
3965 let t = if n == 1 {
3966 0.0
3967 } else {
3968 i as f64 / (n - 1) as f64
3969 };
3970 let x = extent.x_min + t * (extent.x_max - extent.x_min);
3971 ring.push((x, extent.y_min));
3972 }
3973
3974 for i in 0..n {
3975 let t = if n == 1 {
3976 0.0
3977 } else {
3978 i as f64 / (n - 1) as f64
3979 };
3980 let y = extent.y_min + t * (extent.y_max - extent.y_min);
3981 ring.push((extent.x_max, y));
3982 }
3983
3984 for i in 0..n {
3985 let t = if n == 1 {
3986 0.0
3987 } else {
3988 i as f64 / (n - 1) as f64
3989 };
3990 let x = extent.x_max - t * (extent.x_max - extent.x_min);
3991 ring.push((x, extent.y_max));
3992 }
3993
3994 for i in 0..n {
3995 let t = if n == 1 {
3996 0.0
3997 } else {
3998 i as f64 / (n - 1) as f64
3999 };
4000 let y = extent.y_max - t * (extent.y_max - extent.y_min);
4001 ring.push((extent.x_min, y));
4002 }
4003
4004 ring
4005}
4006
4007fn transformed_extent_from_boundary_samples(
4008 src_crs: &Crs,
4009 dst_crs: &Crs,
4010 src_extent: Extent,
4011 samples_per_edge: usize,
4012 dst_epsg: u32,
4013 antimeridian_policy: AntimeridianPolicy,
4014) -> Result<Extent> {
4015 let points = sample_extent_boundary_points(src_extent, samples_per_edge);
4016
4017 let mut tx_min = f64::INFINITY;
4018 let mut tx_max = f64::NEG_INFINITY;
4019 let mut ty_min = f64::INFINITY;
4020 let mut ty_max = f64::NEG_INFINITY;
4021 let mut tx_values = Vec::new();
4022 let mut valid = 0usize;
4023
4024 for (x, y) in points {
4025 let Ok((tx, ty)) = src_crs.transform_to(x, y, dst_crs) else {
4026 continue;
4027 };
4028 if !tx.is_finite() || !ty.is_finite() {
4029 continue;
4030 }
4031 tx_min = tx_min.min(tx);
4032 tx_max = tx_max.max(tx);
4033 ty_min = ty_min.min(ty);
4034 ty_max = ty_max.max(ty);
4035 tx_values.push(tx);
4036 valid += 1;
4037 }
4038
4039 if valid == 0 {
4040 return Err(RasterError::Other(format!(
4041 "failed to transform source extent boundary samples to EPSG:{}",
4042 dst_epsg
4043 )));
4044 }
4045
4046 if dst_epsg == 4326 {
4047 if let Some((x0, x1)) = antimeridian_aware_longitude_bounds(
4048 &tx_values,
4049 antimeridian_policy,
4050 ) {
4051 tx_min = x0;
4052 tx_max = x1;
4053 }
4054 }
4055
4056 Ok(Extent {
4057 x_min: tx_min,
4058 y_min: ty_min,
4059 x_max: tx_max,
4060 y_max: ty_max,
4061 })
4062}
4063
4064fn transformed_boundary_ring_samples(
4065 src_crs: &Crs,
4066 dst_crs: &Crs,
4067 src_extent: Extent,
4068 samples_per_edge: usize,
4069 dst_epsg: u32,
4070 antimeridian_policy: AntimeridianPolicy,
4071) -> Result<Vec<(f64, f64)>> {
4072 let ring = sample_extent_boundary_ring(src_extent, samples_per_edge);
4073 let mut transformed = Vec::with_capacity(ring.len());
4074
4075 for (x, y) in ring {
4076 let Ok((tx, ty)) = src_crs.transform_to(x, y, dst_crs) else {
4077 continue;
4078 };
4079 if tx.is_finite() && ty.is_finite() {
4080 transformed.push((tx, ty));
4081 }
4082 }
4083
4084 if transformed.len() < 3 {
4085 return Err(RasterError::Other(format!(
4086 "failed to build transformed boundary ring for EPSG:{}",
4087 dst_epsg
4088 )));
4089 }
4090
4091 if dst_epsg == 4326 && antimeridian_policy != AntimeridianPolicy::Linear {
4092 let lons: Vec<f64> = transformed.iter().map(|(x, _)| *x).collect();
4093 if let Some((base, _)) = minimal_wrapped_longitude_bounds(&lons) {
4094 for (x, _) in &mut transformed {
4095 let mut w = wrap_lon_360(*x);
4096 if w < base {
4097 w += 360.0;
4098 }
4099 *x = w;
4100 }
4101 }
4102 }
4103
4104 Ok(transformed)
4105}
4106
4107fn snap_down_to_origin(value: f64, origin: f64, step: f64) -> f64 {
4108 origin + ((value - origin) / step).floor() * step
4109}
4110
4111fn snap_up_to_origin(value: f64, origin: f64, step: f64) -> f64 {
4112 origin + ((value - origin) / step).ceil() * step
4113}
4114
4115fn wrap_lon_360(lon: f64) -> f64 {
4116 let mut v = lon % 360.0;
4117 if v < 0.0 {
4118 v += 360.0;
4119 }
4120 v
4121}
4122
4123fn minimal_wrapped_longitude_bounds(longitudes: &[f64]) -> Option<(f64, f64)> {
4124 if longitudes.is_empty() {
4125 return None;
4126 }
4127
4128 if longitudes.len() == 1 {
4129 let v = wrap_lon_360(longitudes[0]);
4130 return Some((v, v));
4131 }
4132
4133 let mut values: Vec<f64> = longitudes.iter().map(|v| wrap_lon_360(*v)).collect();
4134 values.sort_by(f64::total_cmp);
4135
4136 let n = values.len();
4137 let mut max_gap = f64::NEG_INFINITY;
4138 let mut max_gap_idx = 0usize;
4139 for i in 0..n {
4140 let next = if i + 1 < n {
4141 values[i + 1]
4142 } else {
4143 values[0] + 360.0
4144 };
4145 let gap = next - values[i];
4146 if gap > max_gap {
4147 max_gap = gap;
4148 max_gap_idx = i;
4149 }
4150 }
4151
4152 let start = values[(max_gap_idx + 1) % n];
4153 let mut end = values[max_gap_idx];
4154 if end < start {
4155 end += 360.0;
4156 }
4157
4158 Some((start, end))
4159}
4160
4161fn antimeridian_aware_longitude_bounds(
4162 longitudes: &[f64],
4163 policy: AntimeridianPolicy,
4164) -> Option<(f64, f64)> {
4165 if longitudes.is_empty() {
4166 return None;
4167 }
4168
4169 let mut lin_min = f64::INFINITY;
4170 let mut lin_max = f64::NEG_INFINITY;
4171 for lon in longitudes {
4172 if !lon.is_finite() {
4173 continue;
4174 }
4175 lin_min = lin_min.min(*lon);
4176 lin_max = lin_max.max(*lon);
4177 }
4178 if !lin_min.is_finite() || !lin_max.is_finite() {
4179 return None;
4180 }
4181
4182 if policy == AntimeridianPolicy::Linear {
4183 return Some((lin_min, lin_max));
4184 }
4185
4186 let Some((wrap_min, wrap_max)) = minimal_wrapped_longitude_bounds(longitudes) else {
4187 return Some((lin_min, lin_max));
4188 };
4189 if policy == AntimeridianPolicy::Wrap {
4190 return Some((wrap_min, wrap_max));
4191 }
4192
4193 let linear_width = lin_max - lin_min;
4194 let wrapped_width = wrap_max - wrap_min;
4195
4196 if wrapped_width + 1e-9 < linear_width {
4197 Some((wrap_min, wrap_max))
4198 } else {
4199 Some((lin_min, lin_max))
4200 }
4201}
4202
4203fn clamp_isize(v: isize, min_v: isize, max_v: isize) -> isize {
4204 v.max(min_v).min(max_v)
4205}
4206
4207fn point_in_polygon(x: f64, y: f64, polygon: &[(f64, f64)]) -> bool {
4208 if polygon.len() < 3 {
4209 return false;
4210 }
4211 let mut inside = false;
4212 let mut j = polygon.len() - 1;
4213 for i in 0..polygon.len() {
4214 let (xi, yi) = polygon[i];
4215 let (xj, yj) = polygon[j];
4216 let intersects = (yi > y) != (yj > y)
4217 && x < (xj - xi) * (y - yi) / ((yj - yi) + 1e-30) + xi;
4218 if intersects {
4219 inside = !inside;
4220 }
4221 j = i;
4222 }
4223 inside
4224}
4225
4226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4227enum WindowStat {
4228 Mean,
4229 Min,
4230 Max,
4231 Mode,
4232 Median,
4233 StdDev,
4234}
4235
4236fn reduce_window_values(values: &[f64], stat: WindowStat) -> Option<f64> {
4237 if values.is_empty() {
4238 return None;
4239 }
4240
4241 match stat {
4242 WindowStat::Mean => Some(values.iter().sum::<f64>() / values.len() as f64),
4243 WindowStat::Min => values.iter().copied().reduce(f64::min),
4244 WindowStat::Max => values.iter().copied().reduce(f64::max),
4245 WindowStat::Mode => {
4246 let mut pairs: Vec<(f64, usize)> = Vec::new();
4247 for v in values {
4248 if let Some((_, count)) = pairs.iter_mut().find(|(u, _)| *u == *v) {
4249 *count += 1;
4250 } else {
4251 pairs.push((*v, 1));
4252 }
4253 }
4254 pairs.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.total_cmp(&b.0)));
4255 pairs.first().map(|(v, _)| *v)
4256 }
4257 WindowStat::Median => {
4258 let mut sorted = values.to_vec();
4259 sorted.sort_by(f64::total_cmp);
4260 let n = sorted.len();
4261 if n % 2 == 1 {
4262 Some(sorted[n / 2])
4263 } else {
4264 Some((sorted[n / 2 - 1] + sorted[n / 2]) / 2.0)
4265 }
4266 }
4267 WindowStat::StdDev => {
4268 let n = values.len() as f64;
4269 let mean = values.iter().sum::<f64>() / n;
4270 let variance = values
4271 .iter()
4272 .map(|v| {
4273 let d = *v - mean;
4274 d * d
4275 })
4276 .sum::<f64>()
4277 / n;
4278 Some(variance.max(0.0).sqrt())
4279 }
4280 }
4281}
4282
4283impl std::fmt::Display for Raster {
4284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4285 write!(
4286 f,
4287 "Raster({}×{}×{}, cell={:.6}, x=[{:.4},{:.4}], y=[{:.4},{:.4}], type={}, nodata={})",
4288 self.bands,
4289 self.cols,
4290 self.rows,
4291 self.cell_size_x,
4292 self.x_min,
4293 self.x_max(),
4294 self.y_min,
4295 self.y_max(),
4296 self.data_type,
4297 self.nodata,
4298 )
4299 }
4300}
4301
4302#[cfg(test)]
4303mod tests {
4304 use super::*;
4305 use std::sync::{Arc, Mutex};
4306
4307 fn make_raster() -> Raster {
4308 let cfg = RasterConfig { cols: 4, rows: 3, cell_size: 10.0, nodata: -9999.0, ..Default::default() };
4309 let mut r = Raster::new(cfg);
4310 for row in 0..3 {
4311 for col in 0..4 {
4312 let _ = r.set(0, row, col, (row * 4 + col) as f64);
4313 }
4314 }
4315 r
4316 }
4317
4318 #[test]
4319 fn get_set() {
4320 let mut r = make_raster();
4321 assert_eq!(r.get(0, 0, 0), 0.0);
4322 assert_eq!(r.get(0, 2, 3), 11.0);
4323 r.set(0, 1, 1, -9999.0).unwrap();
4324 assert!(r.is_nodata(r.get(0, 1, 1))); assert_eq!(r.get_opt(0, 1, 1), None); }
4327
4328 #[test]
4329 fn statistics() {
4330 let r = make_raster();
4331 let s = r.statistics();
4332 assert_eq!(s.valid_count, 12);
4333 assert_eq!(s.min, 0.0);
4334 assert_eq!(s.max, 11.0);
4335 assert!((s.mean - 5.5).abs() < 1e-10);
4336 }
4337
4338 #[test]
4339 fn world_to_pixel() {
4340 let r = make_raster();
4341 assert_eq!(r.world_to_pixel(5.0, 25.0), Some((0, 0)));
4343 assert_eq!(r.world_to_pixel(35.0, 5.0), Some((3, 2)));
4344 assert_eq!(r.world_to_pixel(-1.0, 0.0), None);
4345 }
4346
4347 #[test]
4348 fn extent() {
4349 let r = make_raster();
4350 let e = r.extent();
4351 assert_eq!(e.x_min, 0.0);
4352 assert_eq!(e.y_min, 0.0);
4353 assert_eq!(e.x_max, 40.0);
4354 assert_eq!(e.y_max, 30.0);
4355 }
4356
4357 #[test]
4358 fn sample_extent_boundary_points_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 pts = sample_extent_boundary_points(e, 8);
4366 assert_eq!(pts.len(), 32);
4367 assert!(pts.contains(&(0.0, 0.0)));
4368 assert!(pts.contains(&(0.0, 5.0)));
4369 assert!(pts.contains(&(10.0, 0.0)));
4370 assert!(pts.contains(&(10.0, 5.0)));
4371 }
4372
4373 #[test]
4374 fn sample_extent_boundary_points_minimum_sampling_when_zero_requested() {
4375 let e = Extent {
4376 x_min: -1.0,
4377 y_min: -2.0,
4378 x_max: 3.0,
4379 y_max: 4.0,
4380 };
4381 let pts = sample_extent_boundary_points(e, 0);
4382 assert_eq!(pts.len(), 4);
4383 assert!(pts.contains(&(-1.0, -2.0)));
4384 assert!(pts.contains(&(-1.0, 4.0)));
4385 assert!(pts.contains(&(3.0, -2.0)));
4386 assert!(pts.contains(&(3.0, 4.0)));
4387 }
4388
4389 #[test]
4390 fn minimal_wrapped_longitude_bounds_handles_antimeridian_cluster() {
4391 let lons = [179.0, -179.0, 178.0, -178.0];
4392 let (x0, x1) = minimal_wrapped_longitude_bounds(&lons).unwrap();
4393 assert!((x1 - x0) < 6.0);
4394 }
4395
4396 #[test]
4397 fn antimeridian_aware_longitude_bounds_prefers_wrapped_interval() {
4398 let lons = [179.5, -179.5, 179.0, -179.0];
4399 let (x0, x1) =
4400 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Auto).unwrap();
4401 assert!((x1 - x0) < 5.0);
4402 assert!(x0 >= 0.0);
4403 }
4404
4405 #[test]
4406 fn antimeridian_aware_longitude_bounds_keeps_linear_when_better() {
4407 let lons = [-20.0, -10.0, 0.0, 10.0];
4408 let (x0, x1) =
4409 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Auto).unwrap();
4410 assert!((x0 - (-20.0)).abs() < 1e-12);
4411 assert!((x1 - 10.0).abs() < 1e-12);
4412 }
4413
4414 #[test]
4415 fn antimeridian_policy_controls_wrapped_vs_linear_behavior() {
4416 let lons = [179.5, -179.5, 179.0, -179.0];
4417
4418 let (ax0, ax1) =
4419 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Auto).unwrap();
4420 let (lx0, lx1) =
4421 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Linear).unwrap();
4422 let (wx0, wx1) =
4423 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Wrap).unwrap();
4424
4425 assert!((ax1 - ax0) < (lx1 - lx0));
4426 assert!((wx1 - wx0) < (lx1 - lx0));
4427 assert!((wx1 - wx0 - (ax1 - ax0)).abs() < 1e-9);
4428 }
4429
4430 #[test]
4431 fn reproject_antimeridian_policy_linear_vs_wrap_changes_default_extent() {
4432 let cfg = RasterConfig {
4433 cols: 8,
4434 rows: 4,
4435 x_min: 170.0,
4436 y_min: -10.0,
4437 cell_size: 2.5,
4438 nodata: -9999.0,
4439 ..Default::default()
4440 };
4441 let mut r = Raster::new(cfg);
4442 r.crs = CrsInfo::from_epsg(4326);
4443 for row in 0..r.rows {
4444 for col in 0..r.cols {
4445 r.set(0, row as isize, col as isize, (row * r.cols + col) as f64)
4446 .unwrap();
4447 }
4448 }
4449
4450 let linear_opts = ReprojectOptions::new(4326, ResampleMethod::Nearest)
4451 .with_antimeridian_policy(AntimeridianPolicy::Linear);
4452 let wrap_opts = ReprojectOptions::new(4326, ResampleMethod::Nearest)
4453 .with_antimeridian_policy(AntimeridianPolicy::Wrap);
4454
4455 let linear = r.reproject_with_options(&linear_opts).unwrap();
4456 let wrap = r.reproject_with_options(&wrap_opts).unwrap();
4457
4458 let linear_width = linear.x_max() - linear.x_min;
4459 let wrap_width = wrap.x_max() - wrap.x_min;
4460
4461 assert!(linear_width.is_finite() && linear_width > 0.0);
4462 assert!(wrap_width.is_finite() && wrap_width > 0.0);
4463 assert!(wrap_width <= linear_width + 1e-9);
4464 }
4465
4466 #[test]
4467 fn reproject_antimeridian_policy_auto_matches_narrower_interval() {
4468 let cfg = RasterConfig {
4469 cols: 8,
4470 rows: 4,
4471 x_min: 170.0,
4472 y_min: -10.0,
4473 cell_size: 2.5,
4474 nodata: -9999.0,
4475 ..Default::default()
4476 };
4477 let mut r = Raster::new(cfg);
4478 r.crs = CrsInfo::from_epsg(4326);
4479 for row in 0..r.rows {
4480 for col in 0..r.cols {
4481 r.set(0, row as isize, col as isize, (row * r.cols + col) as f64)
4482 .unwrap();
4483 }
4484 }
4485
4486 let linear = r
4487 .reproject_with_options(
4488 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
4489 .with_antimeridian_policy(AntimeridianPolicy::Linear),
4490 )
4491 .unwrap();
4492 let wrap = r
4493 .reproject_with_options(
4494 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
4495 .with_antimeridian_policy(AntimeridianPolicy::Wrap),
4496 )
4497 .unwrap();
4498 let auto = r
4499 .reproject_with_options(
4500 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
4501 .with_antimeridian_policy(AntimeridianPolicy::Auto),
4502 )
4503 .unwrap();
4504
4505 let linear_width = linear.x_max() - linear.x_min;
4506 let wrap_width = wrap.x_max() - wrap.x_min;
4507 let auto_width = auto.x_max() - auto.x_min;
4508
4509 let expected = linear_width.min(wrap_width);
4510 assert!((auto_width - expected).abs() < 1e-9);
4511 }
4512
4513 #[test]
4514 fn reproject_to_epsg_requires_source_epsg() {
4515 let r = make_raster();
4516 let err = r.reproject_to_epsg_nearest(3857).unwrap_err();
4517 assert!(err
4518 .to_string()
4519 .contains("requires source CRS EPSG in raster.crs.epsg"));
4520 }
4521
4522 #[test]
4523 fn reproject_with_crs_allows_missing_source_epsg() {
4524 let cfg = RasterConfig {
4525 cols: 4,
4526 rows: 4,
4527 x_min: -1.0,
4528 y_min: -1.0,
4529 cell_size: 0.5,
4530 nodata: -9999.0,
4531 ..Default::default()
4532 };
4533 let mut r = Raster::new(cfg);
4534 for row in 0..4 {
4536 for col in 0..4 {
4537 r.set(0, row, col, (row * 4 + col) as f64).unwrap();
4538 }
4539 }
4540
4541 let src = Crs::from_epsg(4326).unwrap();
4542 let dst = Crs::from_epsg(3857).unwrap();
4543 let opts = ReprojectOptions::new(3857, ResampleMethod::Nearest);
4544
4545 let out = r.reproject_with_crs(&src, &dst, &opts).unwrap();
4546 assert_eq!(out.cols, 4);
4547 assert_eq!(out.rows, 4);
4548 assert_eq!(out.crs.epsg, Some(3857));
4549 assert!(out.statistics().valid_count > 0);
4550 }
4551
4552 #[test]
4553 fn reproject_with_options_and_progress_emits_live_updates() {
4554 let cfg = RasterConfig {
4555 cols: 6,
4556 rows: 5,
4557 x_min: -80.0,
4558 y_min: 40.0,
4559 cell_size: 0.1,
4560 nodata: -9999.0,
4561 ..Default::default()
4562 };
4563 let mut r = Raster::new(cfg);
4564 r.crs = CrsInfo::from_epsg(4326);
4565 for row in 0..r.rows {
4566 for col in 0..r.cols {
4567 r.set(0, row as isize, col as isize, (row * r.cols + col) as f64)
4568 .unwrap();
4569 }
4570 }
4571
4572 let progress_values: Arc<Mutex<Vec<f64>>> = Arc::new(Mutex::new(Vec::new()));
4573 let sink = Arc::clone(&progress_values);
4574
4575 let out = r
4576 .reproject_with_options_and_progress(
4577 &ReprojectOptions::new(3857, ResampleMethod::Nearest),
4578 move |pct| {
4579 sink.lock().unwrap().push(pct);
4580 },
4581 )
4582 .unwrap();
4583
4584 let values = progress_values.lock().unwrap();
4585 assert!(!values.is_empty());
4586 assert_eq!(values.len(), out.rows + 1);
4587 assert!(values.iter().all(|v| v.is_finite() && *v >= 0.0 && *v <= 1.0));
4588 assert!((values.last().copied().unwrap() - 1.0).abs() < 1e-12);
4589 }
4590
4591 #[test]
4592 fn reproject_to_epsg_identity_preserves_data() {
4593 let mut r = make_raster();
4594 r.crs = CrsInfo::from_epsg(4326);
4595
4596 let r2 = r.reproject_to_epsg(4326, ResampleMethod::Nearest).unwrap();
4597 assert_eq!(r2.cols, r.cols);
4598 assert_eq!(r2.rows, r.rows);
4599 assert_eq!(r2.bands, r.bands);
4600 assert_eq!(r2.crs.epsg, Some(4326));
4601 assert_eq!(r2.get(0, 0, 0), r.get(0, 0, 0));
4602 assert_eq!(r2.get(0, 2, 3), r.get(0, 2, 3));
4603 }
4604
4605 #[test]
4606 fn reproject_to_epsg_4326_to_3857_produces_valid_output() {
4607 let cfg = RasterConfig {
4608 cols: 4,
4609 rows: 4,
4610 x_min: -1.0,
4611 y_min: -1.0,
4612 cell_size: 0.5,
4613 nodata: -9999.0,
4614 ..Default::default()
4615 };
4616 let mut r = Raster::new(cfg);
4617 r.crs = CrsInfo::from_epsg(4326);
4618 for row in 0..4 {
4619 for col in 0..4 {
4620 r.set(0, row, col, (row * 4 + col) as f64).unwrap();
4621 }
4622 }
4623
4624 let out = r.reproject_to_epsg_nearest(3857).unwrap();
4625 assert_eq!(out.cols, 4);
4626 assert_eq!(out.rows, 4);
4627 assert_eq!(out.crs.epsg, Some(3857));
4628 assert!(out.x_min.is_finite());
4629 assert!(out.y_min.is_finite());
4630 assert!(out.cell_size_x.is_finite() && out.cell_size_x > 0.0);
4631 assert!(out.cell_size_y.is_finite() && out.cell_size_y > 0.0);
4632 let s = out.statistics();
4633 assert!(s.valid_count > 0);
4634 }
4635
4636 #[test]
4637 fn reproject_to_match_grid_honors_target_grid_definition() {
4638 let src_cfg = RasterConfig {
4639 cols: 6,
4640 rows: 4,
4641 x_min: -3.0,
4642 y_min: -2.0,
4643 cell_size: 1.0,
4644 nodata: -9999.0,
4645 ..Default::default()
4646 };
4647 let mut src = Raster::new(src_cfg);
4648 src.crs = CrsInfo::from_epsg(4326);
4649 for row in 0..src.rows {
4650 for col in 0..src.cols {
4651 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
4652 .unwrap();
4653 }
4654 }
4655
4656 let target_cfg = RasterConfig {
4657 cols: 12,
4658 rows: 10,
4659 x_min: -500_000.0,
4660 y_min: -400_000.0,
4661 cell_size: 1.0,
4662 cell_size_y: Some(1.0),
4663 nodata: -9999.0,
4664 ..Default::default()
4665 };
4666 let mut target = Raster::new(target_cfg);
4667 target.crs = CrsInfo::from_epsg(3857);
4668 target.cell_size_x = (500_000.0 - (-500_000.0)) / target.cols as f64;
4669 target.cell_size_y = (400_000.0 - (-400_000.0)) / target.rows as f64;
4670
4671 let out = src
4672 .reproject_to_match_grid(&target, ResampleMethod::Bilinear)
4673 .unwrap();
4674
4675 assert_eq!(out.cols, target.cols);
4676 assert_eq!(out.rows, target.rows);
4677 assert_eq!(out.crs.epsg, target.crs.epsg);
4678 assert!((out.x_min - target.x_min).abs() < 1e-9);
4679 assert!((out.y_min - target.y_min).abs() < 1e-9);
4680 assert!((out.x_max() - target.x_max()).abs() < 1e-6);
4681 assert!((out.y_max() - target.y_max()).abs() < 1e-6);
4682 assert!(out.cell_size_x.is_finite() && out.cell_size_x > 0.0);
4683 assert!(out.cell_size_y.is_finite() && out.cell_size_y > 0.0);
4684 }
4685
4686 #[test]
4687 fn reproject_to_match_resolution_honors_reference_cellsize_and_snap() {
4688 let src_cfg = RasterConfig {
4689 cols: 6,
4690 rows: 4,
4691 x_min: -3.0,
4692 y_min: -2.0,
4693 cell_size: 1.0,
4694 nodata: -9999.0,
4695 ..Default::default()
4696 };
4697 let mut src = Raster::new(src_cfg);
4698 src.crs = CrsInfo::from_epsg(4326);
4699 for row in 0..src.rows {
4700 for col in 0..src.cols {
4701 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
4702 .unwrap();
4703 }
4704 }
4705
4706 let mut reference = Raster::new(RasterConfig {
4707 cols: 4,
4708 rows: 3,
4709 x_min: 50_000.0,
4710 y_min: -75_000.0,
4711 cell_size: 100_000.0,
4712 cell_size_y: Some(80_000.0),
4713 nodata: -9999.0,
4714 ..Default::default()
4715 });
4716 reference.crs = CrsInfo::from_epsg(3857);
4717
4718 let out = src
4719 .reproject_to_match_resolution(&reference, ResampleMethod::Nearest)
4720 .unwrap();
4721
4722 assert_eq!(out.crs.epsg, Some(3857));
4723 assert!((out.cell_size_x - reference.cell_size_x).abs() < 1e-9);
4724 assert!((out.cell_size_y - reference.cell_size_y).abs() < 1e-9);
4725
4726 let kx = ((out.x_min - reference.x_min) / reference.cell_size_x).round();
4727 let ky = ((out.y_min - reference.y_min) / reference.cell_size_y).round();
4728 assert!((out.x_min - (reference.x_min + kx * reference.cell_size_x)).abs() < 1e-6);
4729 assert!((out.y_min - (reference.y_min + ky * reference.cell_size_y)).abs() < 1e-6);
4730 assert!(out.cols > 0);
4731 assert!(out.rows > 0);
4732 }
4733
4734 #[test]
4735 fn reproject_to_match_resolution_in_epsg_same_crs_matches_reference_settings() {
4736 let src_cfg = RasterConfig {
4737 cols: 6,
4738 rows: 4,
4739 x_min: -3.0,
4740 y_min: -2.0,
4741 cell_size: 1.0,
4742 nodata: -9999.0,
4743 ..Default::default()
4744 };
4745 let mut src = Raster::new(src_cfg);
4746 src.crs = CrsInfo::from_epsg(4326);
4747 for row in 0..src.rows {
4748 for col in 0..src.cols {
4749 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
4750 .unwrap();
4751 }
4752 }
4753
4754 let mut reference = Raster::new(RasterConfig {
4755 cols: 4,
4756 rows: 3,
4757 x_min: -10.0,
4758 y_min: -20.0,
4759 cell_size: 0.5,
4760 cell_size_y: Some(0.25),
4761 nodata: -9999.0,
4762 ..Default::default()
4763 });
4764 reference.crs = CrsInfo::from_epsg(4326);
4765
4766 let out = src
4767 .reproject_to_match_resolution_in_epsg(4326, &reference, ResampleMethod::Nearest)
4768 .unwrap();
4769
4770 assert_eq!(out.crs.epsg, Some(4326));
4771 assert!((out.cell_size_x - reference.cell_size_x).abs() < 1e-12);
4772 assert!((out.cell_size_y - reference.cell_size_y).abs() < 1e-12);
4773 }
4774
4775 #[test]
4776 fn reproject_to_match_resolution_in_epsg_cross_crs_converts_resolution() {
4777 let src_cfg = RasterConfig {
4778 cols: 6,
4779 rows: 4,
4780 x_min: -3.0,
4781 y_min: -2.0,
4782 cell_size: 1.0,
4783 nodata: -9999.0,
4784 ..Default::default()
4785 };
4786 let mut src = Raster::new(src_cfg);
4787 src.crs = CrsInfo::from_epsg(4326);
4788 for row in 0..src.rows {
4789 for col in 0..src.cols {
4790 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
4791 .unwrap();
4792 }
4793 }
4794
4795 let mut reference = Raster::new(RasterConfig {
4796 cols: 4,
4797 rows: 3,
4798 x_min: 0.0,
4799 y_min: 0.0,
4800 cell_size: 1.0,
4801 cell_size_y: Some(1.0),
4802 nodata: -9999.0,
4803 ..Default::default()
4804 });
4805 reference.crs = CrsInfo::from_epsg(4326);
4806
4807 let out = src
4808 .reproject_to_match_resolution_in_epsg(3857, &reference, ResampleMethod::Nearest)
4809 .unwrap();
4810
4811 assert_eq!(out.crs.epsg, Some(3857));
4812 assert!(out.cell_size_x.is_finite());
4813 assert!(out.cell_size_y.is_finite());
4814 assert!(out.cell_size_x > 0.0);
4815 assert!(out.cell_size_y > 0.0);
4816 }
4817
4818 #[test]
4819 fn reproject_to_epsg_bilinear_cubic_and_lanczos_produce_valid_output() {
4820 let cfg = RasterConfig {
4821 cols: 8,
4822 rows: 8,
4823 x_min: -2.0,
4824 y_min: -2.0,
4825 cell_size: 0.5,
4826 nodata: -9999.0,
4827 ..Default::default()
4828 };
4829 let mut r = Raster::new(cfg);
4830 r.crs = CrsInfo::from_epsg(4326);
4831 for row in 0..8 {
4832 for col in 0..8 {
4833 let val = (col as f64) * 10.0 + row as f64;
4834 r.set(0, row, col, val).unwrap();
4835 }
4836 }
4837
4838 let bilinear = r.reproject_to_epsg_bilinear(3857).unwrap();
4839 let cubic = r.reproject_to_epsg_cubic(3857).unwrap();
4840 let lanczos = r.reproject_to_epsg_lanczos(3857).unwrap();
4841
4842 assert_eq!(bilinear.cols, 8);
4843 assert_eq!(bilinear.rows, 8);
4844 assert_eq!(bilinear.crs.epsg, Some(3857));
4845 assert!(bilinear.statistics().valid_count > 0);
4846
4847 assert_eq!(cubic.cols, 8);
4848 assert_eq!(cubic.rows, 8);
4849 assert_eq!(cubic.crs.epsg, Some(3857));
4850 assert!(cubic.statistics().valid_count > 0);
4851
4852 assert_eq!(lanczos.cols, 8);
4853 assert_eq!(lanczos.rows, 8);
4854 assert_eq!(lanczos.crs.epsg, Some(3857));
4855 assert!(lanczos.statistics().valid_count > 0);
4856 }
4857
4858 #[test]
4859 fn reproject_with_options_honors_grid_controls() {
4860 let cfg = RasterConfig {
4861 cols: 6,
4862 rows: 4,
4863 x_min: -3.0,
4864 y_min: -2.0,
4865 cell_size: 1.0,
4866 nodata: -9999.0,
4867 ..Default::default()
4868 };
4869 let mut r = Raster::new(cfg);
4870 r.crs = CrsInfo::from_epsg(4326);
4871 for row in 0..4 {
4872 for col in 0..6 {
4873 r.set(0, row, col, (row * 6 + col) as f64).unwrap();
4874 }
4875 }
4876
4877 let opts = ReprojectOptions {
4878 dst_epsg: 3857,
4879 resample: ResampleMethod::Bilinear,
4880 cols: Some(12),
4881 rows: Some(10),
4882 extent: Some(Extent {
4883 x_min: -500_000.0,
4884 y_min: -400_000.0,
4885 x_max: 500_000.0,
4886 y_max: 400_000.0,
4887 }),
4888 x_res: None,
4889 y_res: None,
4890 snap_x: None,
4891 snap_y: None,
4892 nodata_policy: NodataPolicy::PartialKernel,
4893 antimeridian_policy: AntimeridianPolicy::Auto,
4894 grid_size_policy: GridSizePolicy::Expand,
4895 destination_footprint: DestinationFootprint::None,
4896 warn_on_area_of_use_mismatch: false,
4897 };
4898
4899 let out = r.reproject_with_options(&opts).unwrap();
4900 assert_eq!(out.cols, 12);
4901 assert_eq!(out.rows, 10);
4902 assert!((out.x_min - (-500_000.0)).abs() < 1e-9);
4903 assert!((out.y_min - (-400_000.0)).abs() < 1e-9);
4904 assert!((out.x_max() - 500_000.0).abs() < 1e-6);
4905 assert!((out.y_max() - 400_000.0).abs() < 1e-6);
4906 assert_eq!(out.crs.epsg, Some(3857));
4907 }
4908
4909 #[test]
4910 fn bilinear_partial_kernel_renormalizes_with_nodata() {
4911 let cfg = RasterConfig {
4912 cols: 2,
4913 rows: 2,
4914 x_min: 0.0,
4915 y_min: 0.0,
4916 cell_size: 1.0,
4917 nodata: -9999.0,
4918 ..Default::default()
4919 };
4920 let r = Raster::from_data(cfg, vec![1.0, -9999.0, 3.0, 5.0]).unwrap();
4922
4923 let v = r.sample_bilinear_partial_pixel(0, 0.5, 0.5).unwrap();
4924 assert!((v - 3.0).abs() < 1e-9);
4926 }
4927
4928 #[test]
4929 fn bilinear_partial_kernel_handles_edges() {
4930 let cfg = RasterConfig {
4931 cols: 2,
4932 rows: 2,
4933 x_min: 0.0,
4934 y_min: 0.0,
4935 cell_size: 1.0,
4936 nodata: -9999.0,
4937 ..Default::default()
4938 };
4939 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
4940 let v = r.sample_bilinear_partial_pixel(0, -0.2, 0.3).unwrap();
4941 assert!(v.is_finite());
4942 }
4943
4944 #[test]
4945 fn cubic_partial_kernel_handles_edges_and_nodata() {
4946 let cfg = RasterConfig {
4947 cols: 4,
4948 rows: 4,
4949 x_min: 0.0,
4950 y_min: 0.0,
4951 cell_size: 1.0,
4952 nodata: -9999.0,
4953 ..Default::default()
4954 };
4955 let mut data = Vec::new();
4956 for row in 0..4 {
4957 for col in 0..4 {
4958 data.push((row * 4 + col) as f64);
4959 }
4960 }
4961 data[5] = -9999.0; let r = Raster::from_data(cfg, data).unwrap();
4963
4964 let edge_v = r.sample_cubic_partial_pixel(0, -0.25, 0.2).unwrap();
4965 assert!(edge_v.is_finite());
4966
4967 let nodata_v = r.sample_cubic_partial_pixel(0, 1.25, 1.25).unwrap();
4968 assert!(nodata_v.is_finite());
4969 }
4970
4971 #[test]
4972 fn strict_policy_rejects_incomplete_bilinear_kernel() {
4973 let cfg = RasterConfig {
4974 cols: 2,
4975 rows: 2,
4976 x_min: 0.0,
4977 y_min: 0.0,
4978 cell_size: 1.0,
4979 nodata: -9999.0,
4980 ..Default::default()
4981 };
4982 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
4983
4984 assert!(r.sample_bilinear_strict_pixel(0, -0.2, 0.3).is_none());
4985 assert!(r.sample_bilinear_partial_pixel(0, -0.2, 0.3).is_some());
4986 }
4987
4988 #[test]
4989 fn bilinear_strict_simd_matches_expected_for_f64_and_f32_storage() {
4990 let cfg_f64 = RasterConfig {
4991 cols: 2,
4992 rows: 2,
4993 x_min: 0.0,
4994 y_min: 0.0,
4995 cell_size: 1.0,
4996 nodata: -9999.0,
4997 data_type: DataType::F64,
4998 ..Default::default()
4999 };
5000 let cfg_f32 = RasterConfig {
5001 data_type: DataType::F32,
5002 ..cfg_f64.clone()
5003 };
5004 let data = vec![1.0, 2.0, 3.0, 5.0];
5005 let r64 = Raster::from_data(cfg_f64, data.clone()).unwrap();
5006 let r32 = Raster::from_data(cfg_f32, data).unwrap();
5007
5008 let expected = 2.375;
5009 let v64 = r64.sample_bilinear_strict_pixel(0, 0.25, 0.5).unwrap();
5010 let v32 = r32.sample_bilinear_strict_pixel(0, 0.25, 0.5).unwrap();
5011 assert!((v64 - expected).abs() < 1e-9);
5012 assert!((v32 - expected).abs() < 1e-6);
5013 }
5014
5015 #[test]
5016 fn lanczos_strict_simd_matches_between_f64_and_f32_storage() {
5017 let cfg_f64 = RasterConfig {
5018 cols: 16,
5019 rows: 16,
5020 x_min: 0.0,
5021 y_min: 0.0,
5022 cell_size: 1.0,
5023 nodata: -9999.0,
5024 data_type: DataType::F64,
5025 ..Default::default()
5026 };
5027 let cfg_f32 = RasterConfig {
5028 data_type: DataType::F32,
5029 ..cfg_f64.clone()
5030 };
5031
5032 let mut data = Vec::with_capacity(16 * 16);
5033 for row in 0..16 {
5034 for col in 0..16 {
5035 data.push(((row * 16 + col) as f64).sin() * 100.0 + (row as f64 * 0.5));
5036 }
5037 }
5038
5039 let r64 = Raster::from_data(cfg_f64, data.clone()).unwrap();
5040 let r32 = Raster::from_data(cfg_f32, data).unwrap();
5041
5042 let v64 = r64.sample_lanczos_strict_pixel(0, 7.25, 8.5).unwrap();
5043 let v32 = r32.sample_lanczos_strict_pixel(0, 7.25, 8.5).unwrap();
5044 assert!((v64 - v32).abs() < 1e-4);
5045 }
5046
5047 #[test]
5048 fn fill_policy_falls_back_to_nearest_for_bilinear() {
5049 let cfg = RasterConfig {
5050 cols: 2,
5051 rows: 2,
5052 x_min: 0.0,
5053 y_min: 0.0,
5054 cell_size: 1.0,
5055 nodata: -9999.0,
5056 ..Default::default()
5057 };
5058 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
5059
5060 let v = r.sample_world(0, 0.1, 1.1, ResampleMethod::Bilinear, NodataPolicy::Fill);
5061 assert_eq!(v, Some(10.0));
5062 }
5063
5064 #[test]
5065 fn fill_policy_falls_back_to_nearest_for_lanczos() {
5066 let cfg = RasterConfig {
5067 cols: 2,
5068 rows: 2,
5069 x_min: 0.0,
5070 y_min: 0.0,
5071 cell_size: 1.0,
5072 nodata: -9999.0,
5073 ..Default::default()
5074 };
5075 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
5076
5077 let v = r.sample_world(0, 0.1, 1.1, ResampleMethod::Lanczos, NodataPolicy::Fill);
5078 assert_eq!(v, Some(10.0));
5079 }
5080
5081 #[test]
5082 fn reproject_options_default_to_partial_kernel_policy() {
5083 let opts = ReprojectOptions::new(3857, ResampleMethod::Cubic);
5084 assert_eq!(opts.nodata_policy, NodataPolicy::PartialKernel);
5085 assert_eq!(opts.x_res, None);
5086 assert_eq!(opts.y_res, None);
5087 assert_eq!(opts.snap_x, None);
5088 assert_eq!(opts.snap_y, None);
5089 assert_eq!(opts.antimeridian_policy, AntimeridianPolicy::Auto);
5090 assert_eq!(opts.grid_size_policy, GridSizePolicy::Expand);
5091 assert_eq!(opts.destination_footprint, DestinationFootprint::None);
5092
5093 let strict = opts.with_nodata_policy(NodataPolicy::Strict);
5094 assert_eq!(strict.nodata_policy, NodataPolicy::Strict);
5095
5096 let sized = strict.with_size(321, 123);
5097 assert_eq!(sized.cols, Some(321));
5098 assert_eq!(sized.rows, Some(123));
5099
5100 let e = Extent {
5101 x_min: -10.0,
5102 y_min: -5.0,
5103 x_max: 10.0,
5104 y_max: 5.0,
5105 };
5106 let ext = sized.with_extent(e);
5107 assert_eq!(ext.extent, Some(e));
5108
5109 let res = ext.with_resolution(30.0, 40.0);
5110 assert_eq!(res.x_res, Some(30.0));
5111 assert_eq!(res.y_res, Some(40.0));
5112
5113 let square = res.with_square_resolution(25.0);
5114 assert_eq!(square.x_res, Some(25.0));
5115 assert_eq!(square.y_res, Some(25.0));
5116
5117 let snapped = square.with_snap_origin(0.0, 0.0);
5118 assert_eq!(snapped.snap_x, Some(0.0));
5119 assert_eq!(snapped.snap_y, Some(0.0));
5120
5121 let linear = snapped.with_antimeridian_policy(AntimeridianPolicy::Linear);
5122 assert_eq!(linear.antimeridian_policy, AntimeridianPolicy::Linear);
5123
5124 let fit_inside = linear.with_grid_size_policy(GridSizePolicy::FitInside);
5125 assert_eq!(fit_inside.grid_size_policy, GridSizePolicy::FitInside);
5126
5127 let masked = fit_inside.with_destination_footprint(DestinationFootprint::SourceBoundary);
5128 assert_eq!(masked.destination_footprint, DestinationFootprint::SourceBoundary);
5129 }
5130
5131 #[test]
5132 fn sample_extent_boundary_ring_count_and_corners() {
5133 let e = Extent {
5134 x_min: 0.0,
5135 y_min: 0.0,
5136 x_max: 10.0,
5137 y_max: 5.0,
5138 };
5139 let ring = sample_extent_boundary_ring(e, 8);
5140 assert_eq!(ring.len(), 32);
5141 assert!(ring.contains(&(0.0, 0.0)));
5142 assert!(ring.contains(&(10.0, 0.0)));
5143 assert!(ring.contains(&(10.0, 5.0)));
5144 assert!(ring.contains(&(0.0, 5.0)));
5145 }
5146
5147 #[test]
5148 fn point_in_polygon_identifies_inside_and_outside_points() {
5149 let poly = vec![(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)];
5150 assert!(point_in_polygon(2.0, 2.0, &poly));
5151 assert!(!point_in_polygon(5.0, 2.0, &poly));
5152 }
5153
5154 #[test]
5155 fn thematic_3x3_resamplers_return_expected_statistics() {
5156 let cfg = RasterConfig {
5157 cols: 3,
5158 rows: 3,
5159 x_min: 0.0,
5160 y_min: 0.0,
5161 cell_size: 1.0,
5162 nodata: -9999.0,
5163 ..Default::default()
5164 };
5165 let data = vec![
5166 1.0, 2.0, 2.0,
5167 3.0, 4.0, 4.0,
5168 5.0, 6.0, 6.0,
5169 ];
5170 let expected_mean = data.iter().sum::<f64>() / data.len() as f64;
5171 let expected_stddev = (data
5172 .iter()
5173 .map(|v| {
5174 let d = *v - expected_mean;
5175 d * d
5176 })
5177 .sum::<f64>()
5178 / data.len() as f64)
5179 .sqrt();
5180 let r = Raster::from_data(cfg, data).unwrap();
5181
5182 let x = r.col_center_x(1);
5183 let y = r.row_center_y(1);
5184
5185 let avg = r
5186 .sample_world(0, x, y, ResampleMethod::Average, NodataPolicy::Strict)
5187 .unwrap();
5188 let min = r
5189 .sample_world(0, x, y, ResampleMethod::Min, NodataPolicy::Strict)
5190 .unwrap();
5191 let max = r
5192 .sample_world(0, x, y, ResampleMethod::Max, NodataPolicy::Strict)
5193 .unwrap();
5194 let mode = r
5195 .sample_world(0, x, y, ResampleMethod::Mode, NodataPolicy::Strict)
5196 .unwrap();
5197 let median = r
5198 .sample_world(0, x, y, ResampleMethod::Median, NodataPolicy::Strict)
5199 .unwrap();
5200 let stddev = r
5201 .sample_world(0, x, y, ResampleMethod::StdDev, NodataPolicy::Strict)
5202 .unwrap();
5203
5204 assert!((avg - (33.0 / 9.0)).abs() < 1e-9);
5205 assert_eq!(min, 1.0);
5206 assert_eq!(max, 6.0);
5207 assert_eq!(mode, 2.0);
5208 assert_eq!(median, 4.0);
5209 assert!((stddev - expected_stddev).abs() < 1e-9);
5210 }
5211
5212 #[test]
5213 fn grid_size_policy_fit_inside_reduces_resolution_derived_size() {
5214 let mut r = make_raster();
5215 r.crs = CrsInfo::from_epsg(4326);
5216
5217 let extent = Extent {
5218 x_min: -500_000.0,
5219 y_min: -400_000.0,
5220 x_max: 500_000.0,
5221 y_max: 400_000.0,
5222 };
5223
5224 let expand = r
5225 .reproject_with_options(
5226 &ReprojectOptions::new(3857, ResampleMethod::Nearest)
5227 .with_extent(extent)
5228 .with_resolution(300_000.0, 300_000.0)
5229 .with_grid_size_policy(GridSizePolicy::Expand),
5230 )
5231 .unwrap();
5232
5233 let fit = r
5234 .reproject_with_options(
5235 &ReprojectOptions::new(3857, ResampleMethod::Nearest)
5236 .with_extent(extent)
5237 .with_resolution(300_000.0, 300_000.0)
5238 .with_grid_size_policy(GridSizePolicy::FitInside),
5239 )
5240 .unwrap();
5241
5242 assert!(fit.cols <= expand.cols);
5243 assert!(fit.rows <= expand.rows);
5244 }
5245
5246 #[test]
5247 fn destination_footprint_masks_cells_outside_source_boundary() {
5248 let cfg = RasterConfig {
5249 cols: 4,
5250 rows: 4,
5251 x_min: 0.0,
5252 y_min: 0.0,
5253 cell_size: 1.0,
5254 nodata: -9999.0,
5255 ..Default::default()
5256 };
5257 let mut r = Raster::new(cfg);
5258 r.crs = CrsInfo::from_epsg(4326);
5259 for row in 0..4 {
5260 for col in 0..4 {
5261 r.set(0, row, col, 1.0).unwrap();
5262 }
5263 }
5264
5265 let out = r
5266 .reproject_with_options(
5267 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
5268 .with_extent(Extent {
5269 x_min: -1.0,
5270 y_min: -1.0,
5271 x_max: 5.0,
5272 y_max: 5.0,
5273 })
5274 .with_size(6, 6)
5275 .with_destination_footprint(DestinationFootprint::SourceBoundary),
5276 )
5277 .unwrap();
5278
5279 assert!(out.is_nodata(out.get(0, 0, 0)));
5280 assert!(!out.is_nodata(out.get(0, 2, 2)));
5281 }
5282
5283 #[test]
5284 fn reproject_with_options_honors_snap_origin_with_resolution() {
5285 let cfg = RasterConfig {
5286 cols: 6,
5287 rows: 4,
5288 x_min: -3.0,
5289 y_min: -2.0,
5290 cell_size: 1.0,
5291 nodata: -9999.0,
5292 ..Default::default()
5293 };
5294 let mut r = Raster::new(cfg);
5295 r.crs = CrsInfo::from_epsg(4326);
5296 for row in 0..4 {
5297 for col in 0..6 {
5298 r.set(0, row, col, (row * 6 + col) as f64).unwrap();
5299 }
5300 }
5301
5302 let out_extent = Extent {
5303 x_min: -510_000.0,
5304 y_min: -390_000.0,
5305 x_max: 490_000.0,
5306 y_max: 410_000.0,
5307 };
5308 let opts = ReprojectOptions::new(3857, ResampleMethod::Bilinear)
5309 .with_extent(out_extent)
5310 .with_resolution(200_000.0, 160_000.0)
5311 .with_snap_origin(0.0, 0.0);
5312
5313 let out = r.reproject_with_options(&opts).unwrap();
5314 assert!((out.x_min - (-600_000.0)).abs() < 1e-6);
5315 assert!((out.y_min - (-480_000.0)).abs() < 1e-6);
5316 assert!((out.cell_size_x - 200_000.0).abs() < 1e-6);
5317 assert!((out.cell_size_y - 160_000.0).abs() < 1e-6);
5318 }
5319
5320 #[test]
5321 fn reproject_with_options_honors_resolution_controls() {
5322 let cfg = RasterConfig {
5323 cols: 6,
5324 rows: 4,
5325 x_min: -3.0,
5326 y_min: -2.0,
5327 cell_size: 1.0,
5328 nodata: -9999.0,
5329 ..Default::default()
5330 };
5331 let mut r = Raster::new(cfg);
5332 r.crs = CrsInfo::from_epsg(4326);
5333 for row in 0..4 {
5334 for col in 0..6 {
5335 r.set(0, row, col, (row * 6 + col) as f64).unwrap();
5336 }
5337 }
5338
5339 let out_extent = Extent {
5340 x_min: -500_000.0,
5341 y_min: -400_000.0,
5342 x_max: 500_000.0,
5343 y_max: 400_000.0,
5344 };
5345 let opts = ReprojectOptions::new(3857, ResampleMethod::Bilinear)
5346 .with_extent(out_extent)
5347 .with_resolution(200_000.0, 160_000.0);
5348
5349 let out = r.reproject_with_options(&opts).unwrap();
5350 assert_eq!(out.cols, 5);
5351 assert_eq!(out.rows, 5);
5352 assert!((out.cell_size_x - 200_000.0).abs() < 1e-6);
5353 assert!((out.cell_size_y - 160_000.0).abs() < 1e-6);
5354 }
5355
5356 #[test]
5357 fn reproject_with_options_rejects_invalid_resolution_controls() {
5358 let mut r = make_raster();
5359 r.crs = CrsInfo::from_epsg(4326);
5360 let opts = ReprojectOptions::new(3857, ResampleMethod::Nearest)
5361 .with_square_resolution(0.0);
5362 let err = r.reproject_with_options(&opts).unwrap_err();
5363 assert!(err
5364 .to_string()
5365 .contains("invalid reprojection resolution"));
5366 }
5367
5368 #[test]
5369 fn band_helpers() {
5370 let cfg = RasterConfig {
5371 cols: 3,
5372 rows: 2,
5373 bands: 2,
5374 nodata: -9999.0,
5375 ..Default::default()
5376 };
5377 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];
5378 let mut r = Raster::from_data(cfg, data).unwrap();
5379
5380 let b0 = r.band_slice(0);
5381 assert_eq!(b0.len(), 6);
5382 assert_eq!(b0[0], 1.0);
5383 assert_eq!(b0[5], -9999.0);
5384
5385 let s0 = r.statistics_band(0).unwrap();
5386 assert_eq!(s0.valid_count, 5);
5387 assert_eq!(s0.nodata_count, 1);
5388 assert_eq!(s0.min, 1.0);
5389 assert_eq!(s0.max, 5.0);
5390
5391 r.set_band_slice(1, &[7.0, 8.0, 9.0, 10.0, 11.0, 12.0]).unwrap();
5392 assert_eq!(r.get_raw(1, 0, 0), Some(7.0));
5393 assert_eq!(r.get_raw(1, 1, 2), Some(12.0));
5394 }
5395
5396 #[test]
5397 fn band_transform_helpers() {
5398 let cfg = RasterConfig {
5399 cols: 2,
5400 rows: 2,
5401 bands: 2,
5402 nodata: -9999.0,
5403 ..Default::default()
5404 };
5405 let data = vec![1.0, 2.0, 3.0, -9999.0, 10.0, 20.0, 30.0, 40.0];
5406 let mut r = Raster::from_data(cfg, data).unwrap();
5407
5408 r.map_valid_band(0, |v| v * 2.0).unwrap();
5409 assert_eq!(r.get_raw(0, 0, 0), Some(2.0));
5410 assert_eq!(r.get_raw(0, 0, 1), Some(4.0));
5411 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();
5415 assert_eq!(r.get_raw(1, 0, 1), Some(99.0));
5416 assert_eq!(r.get_raw(1, 0, 0), Some(10.0));
5417 assert_eq!(r.get_raw(0, 0, 1), Some(4.0));
5418 }
5419
5420 #[test]
5421 fn band_iterators() {
5422 let cfg = RasterConfig {
5423 cols: 3,
5424 rows: 2,
5425 bands: 2,
5426 nodata: -9999.0,
5427 ..Default::default()
5428 };
5429 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];
5430 let r = Raster::from_data(cfg, data).unwrap();
5431
5432 let valid_b0: Vec<_> = r.iter_valid_band(0).unwrap().collect();
5433 assert_eq!(valid_b0.len(), 5);
5434 assert_eq!(valid_b0[0], (0, 0, 1.0));
5435 assert_eq!(valid_b0[4], (1, 2, 6.0));
5436
5437 let rows_b1: Vec<Vec<f64>> = r.iter_band_rows(1).unwrap().collect();
5438 assert_eq!(rows_b1.len(), 2);
5439 assert_eq!(rows_b1[0], vec![10.0, 20.0, 30.0]);
5440 assert_eq!(rows_b1[1], vec![40.0, 50.0, 60.0]);
5441 }
5442
5443 #[test]
5444 fn mutable_band_rows_native() {
5445 let cfg = RasterConfig {
5446 cols: 3,
5447 rows: 2,
5448 bands: 1,
5449 data_type: DataType::F32,
5450 nodata: -9999.0,
5451 ..Default::default()
5452 };
5453 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
5454 let mut r = Raster::from_data(cfg, data).unwrap();
5455
5456 r.for_each_band_row_mut(0, |row, row_mut| {
5457 if let RasterRowMut::F32(slice) = row_mut {
5458 for v in slice.iter_mut() {
5459 *v += row as f32;
5460 }
5461 }
5462 })
5463 .unwrap();
5464
5465 assert_eq!(r.get_raw(0, 0, 0), Some(1.0));
5466 assert_eq!(r.get_raw(0, 0, 2), Some(3.0));
5467 assert_eq!(r.get_raw(0, 1, 0), Some(5.0));
5468 assert_eq!(r.get_raw(0, 1, 2), Some(7.0));
5469 }
5470
5471 #[test]
5472 fn immutable_band_rows_native() {
5473 let cfg = RasterConfig {
5474 cols: 3,
5475 rows: 2,
5476 bands: 1,
5477 data_type: DataType::U16,
5478 nodata: 0.0,
5479 ..Default::default()
5480 };
5481 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
5482 let r = Raster::from_data(cfg, data).unwrap();
5483
5484 let mut sums = Vec::new();
5485 r.for_each_band_row(0, |_row, row_ref| {
5486 if let RasterRowRef::U16(slice) = row_ref {
5487 sums.push(slice.iter().map(|v| *v as u64).sum::<u64>());
5488 }
5489 })
5490 .unwrap();
5491
5492 assert_eq!(sums, vec![6, 15]);
5493 }
5494}