1use rayon::prelude::*;
4use std::path::Path;
5use std::sync::atomic::{AtomicUsize, Ordering};
6use wbprojection::{from_proj_string, Crs, CrsTransformPolicy, EpochPolicy, EpochTransformOptions};
7use wide::{f64x4, CmpNe};
8
9use crate::crs_info::CrsInfo;
10use crate::error::{RasterError, Result};
11use crate::formats::RasterFormat;
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() {
137 v.is_nan()
138 } else {
139 v == self.nodata
140 }
141 }
142
143 #[inline]
146 pub fn as_slice(&self) -> &[f64] {
147 &self.data
148 }
149}
150
151unsafe impl Send for BandView {}
153unsafe impl Sync for BandView {}
154
155#[derive(Debug, Clone)]
157pub enum RasterData {
158 U8(Vec<u8>),
160 I8(Vec<i8>),
162 U16(Vec<u16>),
164 I16(Vec<i16>),
166 U32(Vec<u32>),
168 I32(Vec<i32>),
170 U64(Vec<u64>),
172 I64(Vec<i64>),
174 F32(Vec<f32>),
176 F64(Vec<f64>),
178}
179
180pub enum RasterRowMut<'a> {
182 U8(&'a mut [u8]),
184 I8(&'a mut [i8]),
186 U16(&'a mut [u16]),
188 I16(&'a mut [i16]),
190 U32(&'a mut [u32]),
192 I32(&'a mut [i32]),
194 U64(&'a mut [u64]),
196 I64(&'a mut [i64]),
198 F32(&'a mut [f32]),
200 F64(&'a mut [f64]),
202}
203
204pub enum RasterRowRef<'a> {
206 U8(&'a [u8]),
208 I8(&'a [i8]),
210 U16(&'a [u16]),
212 I16(&'a [i16]),
214 U32(&'a [u32]),
216 I32(&'a [i32]),
218 U64(&'a [u64]),
220 I64(&'a [i64]),
222 F32(&'a [f32]),
224 F64(&'a [f64]),
226}
227
228impl RasterData {
229 pub fn new_filled(data_type: DataType, len: usize, value: f64) -> Self {
232 match data_type {
233 DataType::U8 => Self::U8(vec![value as u8; len]),
234 DataType::I8 => Self::I8(vec![value as i8; len]),
235 DataType::U16 => Self::U16(vec![value as u16; len]),
236 DataType::I16 => Self::I16(vec![value as i16; len]),
237 DataType::U32 => Self::U32(vec![value as u32; len]),
238 DataType::I32 => Self::I32(vec![value as i32; len]),
239 DataType::U64 => Self::U64(vec![value as u64; len]),
240 DataType::I64 => Self::I64(vec![value as i64; len]),
241 DataType::F32 => Self::F32(vec![value as f32; len]),
242 DataType::F64 => Self::F64(vec![value; len]),
243 }
244 }
245
246 pub fn new_uninit(data_type: DataType, len: usize) -> Self {
253 fn uninit_vec<T>(len: usize) -> Vec<T> {
254 let mut v = Vec::with_capacity(len);
255 unsafe { v.set_len(len) };
258 v
259 }
260 match data_type {
261 DataType::U8 => Self::U8(uninit_vec(len)),
262 DataType::I8 => Self::I8(uninit_vec(len)),
263 DataType::U16 => Self::U16(uninit_vec(len)),
264 DataType::I16 => Self::I16(uninit_vec(len)),
265 DataType::U32 => Self::U32(uninit_vec(len)),
266 DataType::I32 => Self::I32(uninit_vec(len)),
267 DataType::U64 => Self::U64(uninit_vec(len)),
268 DataType::I64 => Self::I64(uninit_vec(len)),
269 DataType::F32 => Self::F32(uninit_vec(len)),
270 DataType::F64 => Self::F64(uninit_vec(len)),
271 }
272 }
273
274 pub fn from_f64_vec(data_type: DataType, data: Vec<f64>) -> Self {
276 match data_type {
277 DataType::U8 => Self::U8(data.into_iter().map(|v| v as u8).collect()),
278 DataType::I8 => Self::I8(data.into_iter().map(|v| v as i8).collect()),
279 DataType::U16 => Self::U16(data.into_iter().map(|v| v as u16).collect()),
280 DataType::I16 => Self::I16(data.into_iter().map(|v| v as i16).collect()),
281 DataType::U32 => Self::U32(data.into_iter().map(|v| v as u32).collect()),
282 DataType::I32 => Self::I32(data.into_iter().map(|v| v as i32).collect()),
283 DataType::U64 => Self::U64(data.into_iter().map(|v| v as u64).collect()),
284 DataType::I64 => Self::I64(data.into_iter().map(|v| v as i64).collect()),
285 DataType::F32 => Self::F32(data.into_iter().map(|v| v as f32).collect()),
286 DataType::F64 => Self::F64(data),
287 }
288 }
289
290 pub fn len(&self) -> usize {
292 match self {
293 Self::U8(v) => v.len(),
294 Self::I8(v) => v.len(),
295 Self::U16(v) => v.len(),
296 Self::I16(v) => v.len(),
297 Self::U32(v) => v.len(),
298 Self::I32(v) => v.len(),
299 Self::U64(v) => v.len(),
300 Self::I64(v) => v.len(),
301 Self::F32(v) => v.len(),
302 Self::F64(v) => v.len(),
303 }
304 }
305
306 pub fn is_empty(&self) -> bool {
308 self.len() == 0
309 }
310
311 pub fn data_type(&self) -> DataType {
313 match self {
314 Self::U8(_) => DataType::U8,
315 Self::I8(_) => DataType::I8,
316 Self::U16(_) => DataType::U16,
317 Self::I16(_) => DataType::I16,
318 Self::U32(_) => DataType::U32,
319 Self::I32(_) => DataType::I32,
320 Self::U64(_) => DataType::U64,
321 Self::I64(_) => DataType::I64,
322 Self::F32(_) => DataType::F32,
323 Self::F64(_) => DataType::F64,
324 }
325 }
326
327 pub fn get_f64(&self, idx: usize) -> f64 {
329 match self {
330 Self::U8(v) => v[idx] as f64,
331 Self::I8(v) => v[idx] as f64,
332 Self::U16(v) => v[idx] as f64,
333 Self::I16(v) => v[idx] as f64,
334 Self::U32(v) => v[idx] as f64,
335 Self::I32(v) => v[idx] as f64,
336 Self::U64(v) => v[idx] as f64,
337 Self::I64(v) => v[idx] as f64,
338 Self::F32(v) => v[idx] as f64,
339 Self::F64(v) => v[idx],
340 }
341 }
342
343 pub fn set_f64(&mut self, idx: usize, value: f64) {
345 match self {
346 Self::U8(v) => v[idx] = value as u8,
347 Self::I8(v) => v[idx] = value as i8,
348 Self::U16(v) => v[idx] = value as u16,
349 Self::I16(v) => v[idx] = value as i16,
350 Self::U32(v) => v[idx] = value as u32,
351 Self::I32(v) => v[idx] = value as i32,
352 Self::U64(v) => v[idx] = value as u64,
353 Self::I64(v) => v[idx] = value as i64,
354 Self::F32(v) => v[idx] = value as f32,
355 Self::F64(v) => v[idx] = value,
356 }
357 }
358
359 pub fn iter_f64(&self) -> Box<dyn Iterator<Item = f64> + '_> {
361 match self {
362 Self::U8(v) => Box::new(v.iter().copied().map(|x| x as f64)),
363 Self::I8(v) => Box::new(v.iter().copied().map(|x| x as f64)),
364 Self::U16(v) => Box::new(v.iter().copied().map(|x| x as f64)),
365 Self::I16(v) => Box::new(v.iter().copied().map(|x| x as f64)),
366 Self::U32(v) => Box::new(v.iter().copied().map(|x| x as f64)),
367 Self::I32(v) => Box::new(v.iter().copied().map(|x| x as f64)),
368 Self::U64(v) => Box::new(v.iter().copied().map(|x| x as f64)),
369 Self::I64(v) => Box::new(v.iter().copied().map(|x| x as f64)),
370 Self::F32(v) => Box::new(v.iter().copied().map(|x| x as f64)),
371 Self::F64(v) => Box::new(v.iter().copied()),
372 }
373 }
374
375 pub fn to_f64_vec(&self) -> Vec<f64> {
377 self.iter_f64().collect()
378 }
379
380 pub fn as_u8_slice(&self) -> Option<&[u8]> {
382 match self {
383 Self::U8(v) => Some(v.as_slice()),
384 _ => None,
385 }
386 }
387
388 pub fn as_u8_slice_mut(&mut self) -> Option<&mut [u8]> {
390 match self {
391 Self::U8(v) => Some(v.as_mut_slice()),
392 _ => None,
393 }
394 }
395
396 pub fn as_i8_slice(&self) -> Option<&[i8]> {
398 match self {
399 Self::I8(v) => Some(v.as_slice()),
400 _ => None,
401 }
402 }
403
404 pub fn as_i8_slice_mut(&mut self) -> Option<&mut [i8]> {
406 match self {
407 Self::I8(v) => Some(v.as_mut_slice()),
408 _ => None,
409 }
410 }
411
412 pub fn as_u16_slice(&self) -> Option<&[u16]> {
414 match self {
415 Self::U16(v) => Some(v.as_slice()),
416 _ => None,
417 }
418 }
419
420 pub fn as_u16_slice_mut(&mut self) -> Option<&mut [u16]> {
422 match self {
423 Self::U16(v) => Some(v.as_mut_slice()),
424 _ => None,
425 }
426 }
427
428 pub fn as_i16_slice(&self) -> Option<&[i16]> {
430 match self {
431 Self::I16(v) => Some(v.as_slice()),
432 _ => None,
433 }
434 }
435
436 pub fn as_i16_slice_mut(&mut self) -> Option<&mut [i16]> {
438 match self {
439 Self::I16(v) => Some(v.as_mut_slice()),
440 _ => None,
441 }
442 }
443
444 pub fn as_u32_slice(&self) -> Option<&[u32]> {
446 match self {
447 Self::U32(v) => Some(v.as_slice()),
448 _ => None,
449 }
450 }
451
452 pub fn as_u32_slice_mut(&mut self) -> Option<&mut [u32]> {
454 match self {
455 Self::U32(v) => Some(v.as_mut_slice()),
456 _ => None,
457 }
458 }
459
460 pub fn as_i32_slice(&self) -> Option<&[i32]> {
462 match self {
463 Self::I32(v) => Some(v.as_slice()),
464 _ => None,
465 }
466 }
467
468 pub fn as_i32_slice_mut(&mut self) -> Option<&mut [i32]> {
470 match self {
471 Self::I32(v) => Some(v.as_mut_slice()),
472 _ => None,
473 }
474 }
475
476 pub fn as_u64_slice(&self) -> Option<&[u64]> {
478 match self {
479 Self::U64(v) => Some(v.as_slice()),
480 _ => None,
481 }
482 }
483
484 pub fn as_u64_slice_mut(&mut self) -> Option<&mut [u64]> {
486 match self {
487 Self::U64(v) => Some(v.as_mut_slice()),
488 _ => None,
489 }
490 }
491
492 pub fn as_i64_slice(&self) -> Option<&[i64]> {
494 match self {
495 Self::I64(v) => Some(v.as_slice()),
496 _ => None,
497 }
498 }
499
500 pub fn as_i64_slice_mut(&mut self) -> Option<&mut [i64]> {
502 match self {
503 Self::I64(v) => Some(v.as_mut_slice()),
504 _ => None,
505 }
506 }
507
508 pub fn as_f32_slice(&self) -> Option<&[f32]> {
510 match self {
511 Self::F32(v) => Some(v.as_slice()),
512 _ => None,
513 }
514 }
515
516 pub fn as_f32_slice_mut(&mut self) -> Option<&mut [f32]> {
518 match self {
519 Self::F32(v) => Some(v.as_mut_slice()),
520 _ => None,
521 }
522 }
523
524 pub fn as_f64_slice(&self) -> Option<&[f64]> {
526 match self {
527 Self::F64(v) => Some(v.as_slice()),
528 _ => None,
529 }
530 }
531
532 pub fn as_f64_slice_mut(&mut self) -> Option<&mut [f64]> {
534 match self {
535 Self::F64(v) => Some(v.as_mut_slice()),
536 _ => None,
537 }
538 }
539
540 pub fn par_fill_with<F>(&mut self, f: F)
547 where
548 F: Fn(usize) -> f64 + Send + Sync,
549 {
550 match self {
551 Self::U8(v) => v
552 .par_iter_mut()
553 .enumerate()
554 .for_each(|(i, c)| *c = f(i) as u8),
555 Self::I8(v) => v
556 .par_iter_mut()
557 .enumerate()
558 .for_each(|(i, c)| *c = f(i) as i8),
559 Self::U16(v) => v
560 .par_iter_mut()
561 .enumerate()
562 .for_each(|(i, c)| *c = f(i) as u16),
563 Self::I16(v) => v
564 .par_iter_mut()
565 .enumerate()
566 .for_each(|(i, c)| *c = f(i) as i16),
567 Self::U32(v) => v
568 .par_iter_mut()
569 .enumerate()
570 .for_each(|(i, c)| *c = f(i) as u32),
571 Self::I32(v) => v
572 .par_iter_mut()
573 .enumerate()
574 .for_each(|(i, c)| *c = f(i) as i32),
575 Self::U64(v) => v
576 .par_iter_mut()
577 .enumerate()
578 .for_each(|(i, c)| *c = f(i) as u64),
579 Self::I64(v) => v
580 .par_iter_mut()
581 .enumerate()
582 .for_each(|(i, c)| *c = f(i) as i64),
583 Self::F32(v) => v
584 .par_iter_mut()
585 .enumerate()
586 .for_each(|(i, c)| *c = f(i) as f32),
587 Self::F64(v) => v.par_iter_mut().enumerate().for_each(|(i, c)| *c = f(i)),
588 }
589 }
590}
591
592#[derive(Debug, Clone, Copy, PartialEq)]
596pub struct NoData(pub f64);
597
598impl NoData {
599 pub const COMMON: NoData = NoData(-9999.0);
601 pub const NAN: NoData = NoData(f64::NAN);
603
604 #[inline]
606 pub fn matches(self, v: f64) -> bool {
607 if self.0.is_nan() {
608 v.is_nan()
609 } else {
610 (v - self.0).abs() < f64::EPSILON * self.0.abs().max(1.0)
611 }
612 }
613}
614
615impl Default for NoData {
616 fn default() -> Self {
617 Self::COMMON
618 }
619}
620
621#[derive(Debug, Clone, Copy, PartialEq)]
625pub struct Extent {
626 pub x_min: f64,
628 pub y_min: f64,
630 pub x_max: f64,
632 pub y_max: f64,
634}
635
636impl Extent {
637 pub fn from_origin(x_min: f64, y_min: f64, cols: usize, rows: usize, cell_size: f64) -> Self {
639 Self {
640 x_min,
641 y_min,
642 x_max: x_min + cols as f64 * cell_size,
643 y_max: y_min + rows as f64 * cell_size,
644 }
645 }
646
647 pub fn width(&self) -> f64 {
649 self.x_max - self.x_min
650 }
651
652 pub fn height(&self) -> f64 {
654 self.y_max - self.y_min
655 }
656}
657
658#[derive(Debug, Clone, Copy, PartialEq)]
662pub struct Statistics {
663 pub min: f64,
665 pub max: f64,
667 pub mean: f64,
669 pub std_dev: f64,
671 pub valid_count: usize,
673 pub nodata_count: usize,
675}
676
677#[derive(Debug, Clone, Copy, PartialEq, Eq)]
679pub enum StatisticsComputationMode {
680 Auto,
682 Scalar,
684 Simd,
686}
687
688#[derive(Debug, Clone, Copy)]
689struct StatsAccumulator {
690 min: f64,
691 max: f64,
692 sum: f64,
693 sum_sq: f64,
694 valid_count: usize,
695 nodata_count: usize,
696}
697
698impl Default for StatsAccumulator {
699 fn default() -> Self {
700 Self {
701 min: f64::INFINITY,
702 max: f64::NEG_INFINITY,
703 sum: 0.0,
704 sum_sq: 0.0,
705 valid_count: 0,
706 nodata_count: 0,
707 }
708 }
709}
710
711impl StatsAccumulator {
712 fn merge(&mut self, other: Self) {
713 self.min = self.min.min(other.min);
714 self.max = self.max.max(other.max);
715 self.sum += other.sum;
716 self.sum_sq += other.sum_sq;
717 self.valid_count += other.valid_count;
718 self.nodata_count += other.nodata_count;
719 }
720
721 fn to_statistics(self) -> Statistics {
722 let (mean, std_dev) = if self.valid_count == 0 {
723 (0.0, 0.0)
724 } else {
725 let n = self.valid_count as f64;
726 let mean = self.sum / n;
727 let variance = (self.sum_sq / n - mean * mean).max(0.0);
728 (mean, variance.sqrt())
729 };
730
731 Statistics {
732 min: if self.valid_count == 0 { 0.0 } else { self.min },
733 max: if self.valid_count == 0 { 0.0 } else { self.max },
734 mean,
735 std_dev,
736 valid_count: self.valid_count,
737 nodata_count: self.nodata_count,
738 }
739 }
740}
741
742#[derive(Debug, Clone, Copy, PartialEq, Eq)]
744pub enum ResampleMethod {
745 Nearest,
747 Bilinear,
749 Cubic,
751 Lanczos,
753 Average,
755 Min,
757 Max,
759 Mode,
761 Median,
763 StdDev,
765}
766
767#[derive(Debug, Clone, Copy, PartialEq, Eq)]
769pub enum NodataPolicy {
770 Strict,
772 PartialKernel,
774 Fill,
776}
777
778#[derive(Debug, Clone, Copy, PartialEq, Eq)]
780pub enum AntimeridianPolicy {
781 Auto,
783 Linear,
785 Wrap,
787}
788
789#[derive(Debug, Clone, Copy, PartialEq, Eq)]
791pub enum GridSizePolicy {
792 Expand,
794 FitInside,
796}
797
798#[derive(Debug, Clone, Copy, PartialEq, Eq)]
800pub enum DestinationFootprint {
801 None,
803 SourceBoundary,
805}
806
807#[derive(Debug, Clone, Copy)]
809pub struct ReprojectOptions {
810 pub dst_epsg: u32,
812 pub resample: ResampleMethod,
814 pub cols: Option<usize>,
816 pub rows: Option<usize>,
818 pub extent: Option<Extent>,
822 pub x_res: Option<f64>,
826 pub y_res: Option<f64>,
830 pub snap_x: Option<f64>,
834 pub snap_y: Option<f64>,
838 pub nodata_policy: NodataPolicy,
840 pub antimeridian_policy: AntimeridianPolicy,
842 pub grid_size_policy: GridSizePolicy,
844 pub destination_footprint: DestinationFootprint,
846 pub warn_on_area_of_use_mismatch: bool,
849 pub epoch_transform: EpochTransformOptions,
851}
852
853impl ReprojectOptions {
854 pub fn new(dst_epsg: u32, resample: ResampleMethod) -> Self {
856 Self {
857 dst_epsg,
858 resample,
859 cols: None,
860 rows: None,
861 extent: None,
862 x_res: None,
863 y_res: None,
864 snap_x: None,
865 snap_y: None,
866 nodata_policy: NodataPolicy::PartialKernel,
867 antimeridian_policy: AntimeridianPolicy::Auto,
868 grid_size_policy: GridSizePolicy::Expand,
869 destination_footprint: DestinationFootprint::None,
870 warn_on_area_of_use_mismatch: false,
871 epoch_transform: EpochTransformOptions::default(),
872 }
873 }
874
875 pub fn with_nodata_policy(mut self, nodata_policy: NodataPolicy) -> Self {
877 self.nodata_policy = nodata_policy;
878 self
879 }
880
881 pub fn with_size(mut self, cols: usize, rows: usize) -> Self {
883 self.cols = Some(cols);
884 self.rows = Some(rows);
885 self
886 }
887
888 pub fn with_extent(mut self, extent: Extent) -> Self {
890 self.extent = Some(extent);
891 self
892 }
893
894 pub fn with_resolution(mut self, x_res: f64, y_res: f64) -> Self {
899 self.x_res = Some(x_res);
900 self.y_res = Some(y_res);
901 self
902 }
903
904 pub fn with_square_resolution(mut self, res: f64) -> Self {
906 self.x_res = Some(res);
907 self.y_res = Some(res);
908 self
909 }
910
911 pub fn with_snap_origin(mut self, snap_x: f64, snap_y: f64) -> Self {
916 self.snap_x = Some(snap_x);
917 self.snap_y = Some(snap_y);
918 self
919 }
920
921 pub fn with_antimeridian_policy(mut self, policy: AntimeridianPolicy) -> Self {
923 self.antimeridian_policy = policy;
924 self
925 }
926
927 pub fn with_grid_size_policy(mut self, policy: GridSizePolicy) -> Self {
929 self.grid_size_policy = policy;
930 self
931 }
932
933 pub fn with_destination_footprint(mut self, footprint: DestinationFootprint) -> Self {
935 self.destination_footprint = footprint;
936 self
937 }
938
939 pub fn with_area_of_use_warning(mut self, enabled: bool) -> Self {
941 self.warn_on_area_of_use_mismatch = enabled;
942 self
943 }
944
945 pub fn with_epoch_transform_options(mut self, epoch_transform: EpochTransformOptions) -> Self {
947 self.epoch_transform = epoch_transform;
948 self
949 }
950}
951
952#[derive(Debug, Clone)]
956pub struct RasterConfig {
957 pub cols: usize,
959 pub rows: usize,
961 pub bands: usize,
963 pub x_min: f64,
965 pub y_min: f64,
967 pub cell_size: f64,
969 pub cell_size_y: Option<f64>,
972 pub nodata: f64,
974 pub data_type: DataType,
976 pub crs: CrsInfo,
978 pub metadata: Vec<(String, String)>,
980}
981
982impl Default for RasterConfig {
983 fn default() -> Self {
984 Self {
985 cols: 0,
986 rows: 0,
987 bands: 1,
988 x_min: 0.0,
989 y_min: 0.0,
990 cell_size: 1.0,
991 cell_size_y: None,
992 nodata: -9999.0,
993 data_type: DataType::F32,
994 crs: CrsInfo::default(),
995 metadata: Vec::new(),
996 }
997 }
998}
999
1000#[derive(Debug, Clone)]
1010pub struct Raster {
1011 pub cols: usize,
1013 pub rows: usize,
1015 pub bands: usize,
1017 pub x_min: f64,
1019 pub y_min: f64,
1021 pub cell_size_x: f64,
1023 pub cell_size_y: f64,
1025 pub nodata: f64,
1027 pub data_type: DataType,
1029 pub crs: CrsInfo,
1031 pub metadata: Vec<(String, String)>,
1033 pub data: RasterData,
1035}
1036
1037impl Raster {
1038 pub fn new(cfg: RasterConfig) -> Self {
1042 let bands = cfg.bands.max(1);
1043 let n = cfg.cols * cfg.rows * bands;
1044 let cell_size_y = cfg.cell_size_y.map(|v| v.abs()).unwrap_or(cfg.cell_size);
1045 Self {
1046 cols: cfg.cols,
1047 rows: cfg.rows,
1048 bands,
1049 x_min: cfg.x_min,
1050 y_min: cfg.y_min,
1051 cell_size_x: cfg.cell_size,
1052 cell_size_y,
1053 nodata: cfg.nodata,
1054 data_type: cfg.data_type,
1055 crs: cfg.crs,
1056 metadata: cfg.metadata,
1057 data: RasterData::new_filled(cfg.data_type, n, cfg.nodata),
1058 }
1059 }
1060
1061 pub fn from_data(cfg: RasterConfig, data: Vec<f64>) -> Result<Self> {
1066 let bands = cfg.bands.max(1);
1067 if data.len() != cfg.cols * cfg.rows * bands {
1068 return Err(RasterError::InvalidDimensions {
1069 cols: cfg.cols,
1070 rows: cfg.rows,
1071 });
1072 }
1073 let dt = cfg.data_type;
1074 let mut r = Self::new(cfg);
1075 r.data = RasterData::from_f64_vec(dt, data);
1076 Ok(r)
1077 }
1078
1079 pub fn from_data_native(cfg: RasterConfig, data: RasterData) -> Result<Self> {
1085 let bands = cfg.bands.max(1);
1086 if data.len() != cfg.cols * cfg.rows * bands {
1087 return Err(RasterError::InvalidDimensions {
1088 cols: cfg.cols,
1089 rows: cfg.rows,
1090 });
1091 }
1092 if cfg.data_type != data.data_type() {
1093 return Err(RasterError::Other(format!(
1094 "data_type mismatch: config={}, data={}",
1095 cfg.data_type,
1096 data.data_type()
1097 )));
1098 }
1099 let mut r = Self::new(cfg);
1100 r.data = data;
1101 Ok(r)
1102 }
1103
1104 pub fn new_like(template: &Raster) -> Self {
1108 Self::new(RasterConfig {
1109 cols: template.cols,
1110 rows: template.rows,
1111 bands: template.bands,
1112 x_min: template.x_min,
1113 y_min: template.y_min,
1114 cell_size: template.cell_size_x,
1115 cell_size_y: Some(template.cell_size_y),
1116 nodata: template.nodata,
1117 data_type: template.data_type,
1118 crs: template.crs.clone(),
1119 metadata: template.metadata.clone(),
1120 })
1121 }
1122
1123 pub fn new_like_uninit(template: &Raster) -> Self {
1128 let bands = template.bands.max(1);
1129 let n = template.cols * template.rows * bands;
1130 let cell_size_y = template.cell_size_y;
1131 Self {
1132 cols: template.cols,
1133 rows: template.rows,
1134 bands,
1135 x_min: template.x_min,
1136 y_min: template.y_min,
1137 cell_size_x: template.cell_size_x,
1138 cell_size_y,
1139 nodata: template.nodata,
1140 data_type: template.data_type,
1141 crs: template.crs.clone(),
1142 metadata: template.metadata.clone(),
1143 data: RasterData::new_uninit(template.data_type, n),
1144 }
1145 }
1146
1147 pub fn data_u8(&self) -> Option<&[u8]> {
1149 self.data.as_u8_slice()
1150 }
1151 pub fn data_u8_mut(&mut self) -> Option<&mut [u8]> {
1153 self.data.as_u8_slice_mut()
1154 }
1155
1156 pub fn data_i8(&self) -> Option<&[i8]> {
1158 self.data.as_i8_slice()
1159 }
1160 pub fn data_i8_mut(&mut self) -> Option<&mut [i8]> {
1162 self.data.as_i8_slice_mut()
1163 }
1164
1165 pub fn data_u16(&self) -> Option<&[u16]> {
1167 self.data.as_u16_slice()
1168 }
1169 pub fn data_u16_mut(&mut self) -> Option<&mut [u16]> {
1171 self.data.as_u16_slice_mut()
1172 }
1173
1174 pub fn data_i16(&self) -> Option<&[i16]> {
1176 self.data.as_i16_slice()
1177 }
1178 pub fn data_i16_mut(&mut self) -> Option<&mut [i16]> {
1180 self.data.as_i16_slice_mut()
1181 }
1182
1183 pub fn data_u32(&self) -> Option<&[u32]> {
1185 self.data.as_u32_slice()
1186 }
1187 pub fn data_u32_mut(&mut self) -> Option<&mut [u32]> {
1189 self.data.as_u32_slice_mut()
1190 }
1191
1192 pub fn data_i32(&self) -> Option<&[i32]> {
1194 self.data.as_i32_slice()
1195 }
1196 pub fn data_i32_mut(&mut self) -> Option<&mut [i32]> {
1198 self.data.as_i32_slice_mut()
1199 }
1200
1201 pub fn data_u64(&self) -> Option<&[u64]> {
1203 self.data.as_u64_slice()
1204 }
1205 pub fn data_u64_mut(&mut self) -> Option<&mut [u64]> {
1207 self.data.as_u64_slice_mut()
1208 }
1209
1210 pub fn data_i64(&self) -> Option<&[i64]> {
1212 self.data.as_i64_slice()
1213 }
1214 pub fn data_i64_mut(&mut self) -> Option<&mut [i64]> {
1216 self.data.as_i64_slice_mut()
1217 }
1218
1219 pub fn data_f32(&self) -> Option<&[f32]> {
1221 self.data.as_f32_slice()
1222 }
1223 pub fn data_f32_mut(&mut self) -> Option<&mut [f32]> {
1225 self.data.as_f32_slice_mut()
1226 }
1227
1228 pub fn data_f64(&self) -> Option<&[f64]> {
1230 self.data.as_f64_slice()
1231 }
1232 pub fn data_f64_mut(&mut self) -> Option<&mut [f64]> {
1234 self.data.as_f64_slice_mut()
1235 }
1236
1237 pub fn band_view(&self, band: usize) -> BandView {
1247 BandView {
1248 data: self.band_to_vec_f64(band),
1249 rows: self.rows as isize,
1250 cols: self.cols as isize,
1251 nodata: self.nodata,
1252 }
1253 }
1254
1255 #[inline]
1264 pub fn band_as_f64_slice(&self, band: usize) -> Option<&[f64]> {
1265 let stride = self.rows * self.cols;
1266 let start = band * stride;
1267 self.data.as_f64_slice()?.get(start..start + stride)
1268 }
1269
1270 pub fn band_to_vec_f64(&self, band: usize) -> Vec<f64> {
1289 let stride = self.rows * self.cols;
1290 let start = band * stride;
1291 let end = start + stride;
1292 match &self.data {
1293 RasterData::F64(v) => v[start..end].to_vec(),
1294 RasterData::F32(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1295 RasterData::U8(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1296 RasterData::I8(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1297 RasterData::U16(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1298 RasterData::I16(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1299 RasterData::U32(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1300 RasterData::I32(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1301 RasterData::U64(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1302 RasterData::I64(v) => v[start..end].iter().map(|&x| x as f64).collect(),
1303 }
1304 }
1305
1306 pub fn par_fill_with<F>(&mut self, f: F)
1312 where
1313 F: Fn(usize) -> f64 + Send + Sync,
1314 {
1315 self.data.par_fill_with(f);
1316 }
1317
1318 pub fn apply_unary_math<F>(&mut self, f: F, target_bands: Option<Vec<usize>>) -> Result<()>
1329 where
1330 F: Fn(f64) -> f64 + Send + Sync,
1331 {
1332 let nodata = self.nodata;
1333 let nodata_is_nan = nodata.is_nan();
1334
1335 match target_bands {
1336 None => {
1337 if let Some(data) = self.data.as_f32_slice_mut() {
1340 let nodata_f32 = nodata as f32;
1341 data.par_iter_mut().for_each(|v| {
1342 let zf = *v as f64;
1343 let is_nd = if nodata_is_nan {
1344 zf.is_nan()
1345 } else {
1346 *v == nodata_f32
1347 };
1348 if !is_nd {
1349 *v = f(zf) as f32;
1350 }
1351 });
1352 } else if let Some(data) = self.data.as_f64_slice_mut() {
1354 data.par_iter_mut().for_each(|v| {
1355 let is_nd = if nodata_is_nan {
1356 v.is_nan()
1357 } else {
1358 *v == nodata
1359 };
1360 if !is_nd {
1361 *v = f(*v);
1362 }
1363 });
1364 } else {
1366 let len = self.data.len();
1367 for i in 0..len {
1368 let z = self.data.get_f64(i);
1369 if nodata_is_nan {
1370 if !z.is_nan() {
1371 self.data.set_f64(i, f(z));
1372 }
1373 } else if z != nodata {
1374 self.data.set_f64(i, f(z));
1375 }
1376 }
1377 }
1378 }
1379 Some(bands) => {
1380 for band in bands {
1382 if band >= self.bands {
1383 return Err(RasterError::OutOfBounds {
1384 band: band as isize,
1385 row: 0,
1386 col: 0,
1387 bands: self.bands,
1388 cols: self.cols,
1389 rows: self.rows,
1390 });
1391 }
1392 let mut vals = self.band_slice(band as isize);
1393 vals.par_iter_mut().for_each(|v| {
1394 let is_nd = if nodata_is_nan {
1395 v.is_nan()
1396 } else {
1397 *v == nodata
1398 };
1399 if !is_nd {
1400 *v = f(*v);
1401 }
1402 });
1403 self.set_band_slice(band as isize, &vals)?;
1404 }
1405 }
1406 }
1407
1408 Ok(())
1409 }
1410
1411 pub fn apply_unary_math_from<F>(&mut self, f: F, src: &Raster) -> Result<()>
1419 where
1420 F: Fn(f64) -> f64 + Send + Sync,
1421 {
1422 if self.rows != src.rows || self.cols != src.cols || self.bands != src.bands {
1423 return Err(RasterError::Other(format!(
1424 "raster dimension mismatch: self {}×{}×{} != src {}×{}×{}",
1425 self.bands, self.rows, self.cols, src.bands, src.rows, src.cols
1426 )));
1427 }
1428
1429 let nodata = src.nodata;
1430 let nodata_is_nan = nodata.is_nan();
1431
1432 if let (Some(src_data), Some(dst_data)) =
1434 (src.data.as_f32_slice(), self.data.as_f32_slice_mut())
1435 {
1436 let nodata_f32 = nodata as f32;
1437 dst_data
1438 .par_iter_mut()
1439 .zip(src_data.par_iter())
1440 .for_each(|(out, &z)| {
1441 let zf = z as f64;
1442 let is_nd = if nodata_is_nan {
1443 zf.is_nan()
1444 } else {
1445 z == nodata_f32
1446 };
1447 *out = if is_nd { nodata_f32 } else { f(zf) as f32 };
1448 });
1449 } else if let (Some(src_data), Some(dst_data)) =
1451 (src.data.as_f64_slice(), self.data.as_f64_slice_mut())
1452 {
1453 dst_data
1454 .par_iter_mut()
1455 .zip(src_data.par_iter())
1456 .for_each(|(out, &z)| {
1457 let is_nd = if nodata_is_nan {
1458 z.is_nan()
1459 } else {
1460 z == nodata
1461 };
1462 *out = if is_nd { nodata } else { f(z) };
1463 });
1464 } else {
1466 let len = self.data.len();
1467 for i in 0..len {
1468 let z = src.data.get_f64(i);
1469 let result = if nodata_is_nan {
1470 if z.is_nan() {
1471 nodata
1472 } else {
1473 f(z)
1474 }
1475 } else if z == nodata {
1476 nodata
1477 } else {
1478 f(z)
1479 };
1480 self.data.set_f64(i, result);
1481 }
1482 }
1483
1484 Ok(())
1485 }
1486
1487 pub fn apply_binary_math_from<F>(&mut self, f: F, src1: &Raster, src2: &Raster) -> Result<()>
1499 where
1500 F: Fn(f64, f64) -> f64 + Send + Sync,
1501 {
1502 if self.rows != src1.rows
1503 || self.cols != src1.cols
1504 || self.bands != src1.bands
1505 || src1.rows != src2.rows
1506 || src1.cols != src2.cols
1507 || src1.bands != src2.bands
1508 {
1509 return Err(RasterError::Other(format!(
1510 "raster dimension mismatch: self {}×{}×{}, src1 {}×{}×{}, src2 {}×{}×{}",
1511 self.bands,
1512 self.rows,
1513 self.cols,
1514 src1.bands,
1515 src1.rows,
1516 src1.cols,
1517 src2.bands,
1518 src2.rows,
1519 src2.cols,
1520 )));
1521 }
1522
1523 let nd1 = src1.nodata;
1524 let nd1_is_nan = nd1.is_nan();
1525 let nd2 = src2.nodata;
1526 let nd2_is_nan = nd2.is_nan();
1527 let nd_out = self.nodata;
1528
1529 let is_nd1 = |v: f32| -> bool {
1530 if nd1_is_nan {
1531 (v as f64).is_nan()
1532 } else {
1533 v == nd1 as f32
1534 }
1535 };
1536 let is_nd2 = |v: f32| -> bool {
1537 if nd2_is_nan {
1538 (v as f64).is_nan()
1539 } else {
1540 v == nd2 as f32
1541 }
1542 };
1543 let is_nd1_f64 = |v: f64| -> bool {
1544 if nd1_is_nan {
1545 v.is_nan()
1546 } else {
1547 v == nd1
1548 }
1549 };
1550 let is_nd2_f64 = |v: f64| -> bool {
1551 if nd2_is_nan {
1552 v.is_nan()
1553 } else {
1554 v == nd2
1555 }
1556 };
1557
1558 if let (Some(d1), Some(dst)) = (src1.data.as_f32_slice(), self.data.as_f32_slice_mut()) {
1560 let nd_out_f32 = nd_out as f32;
1561
1562 if let Some(d2) = src2.data.as_f32_slice() {
1563 dst.par_iter_mut()
1564 .zip(d1.par_iter())
1565 .zip(d2.par_iter())
1566 .for_each(|((out, &z1), &z2)| {
1567 if is_nd1(z1) || is_nd2(z2) {
1568 *out = nd_out_f32;
1569 } else {
1570 *out = f(z1 as f64, z2 as f64) as f32;
1571 }
1572 });
1573 return Ok(());
1574 }
1575
1576 macro_rules! run_f32_rhs_typed {
1577 ($rhs:expr) => {{
1578 dst.par_iter_mut()
1579 .zip(d1.par_iter())
1580 .zip($rhs.par_iter())
1581 .for_each(|((out, &z1), &z2)| {
1582 let z2f = z2 as f64;
1583 let z2_is_nd = if nd2_is_nan { z2f.is_nan() } else { z2f == nd2 };
1584 if is_nd1(z1) || z2_is_nd {
1585 *out = nd_out_f32;
1586 } else {
1587 *out = f(z1 as f64, z2f) as f32;
1588 }
1589 });
1590 return Ok(());
1591 }};
1592 }
1593
1594 if let Some(d2) = src2.data.as_f64_slice() {
1595 dst.par_iter_mut()
1596 .zip(d1.par_iter())
1597 .zip(d2.par_iter())
1598 .for_each(|((out, &z1), &z2)| {
1599 if is_nd1(z1) || is_nd2_f64(z2) {
1600 *out = nd_out_f32;
1601 } else {
1602 *out = f(z1 as f64, z2) as f32;
1603 }
1604 });
1605 return Ok(());
1606 }
1607 if let Some(d2) = src2.data.as_u8_slice() {
1608 run_f32_rhs_typed!(d2);
1609 }
1610 if let Some(d2) = src2.data.as_i8_slice() {
1611 run_f32_rhs_typed!(d2);
1612 }
1613 if let Some(d2) = src2.data.as_u16_slice() {
1614 run_f32_rhs_typed!(d2);
1615 }
1616 if let Some(d2) = src2.data.as_i16_slice() {
1617 run_f32_rhs_typed!(d2);
1618 }
1619 if let Some(d2) = src2.data.as_u32_slice() {
1620 run_f32_rhs_typed!(d2);
1621 }
1622 if let Some(d2) = src2.data.as_i32_slice() {
1623 run_f32_rhs_typed!(d2);
1624 }
1625 if let Some(d2) = src2.data.as_u64_slice() {
1626 run_f32_rhs_typed!(d2);
1627 }
1628 if let Some(d2) = src2.data.as_i64_slice() {
1629 run_f32_rhs_typed!(d2);
1630 }
1631 }
1632
1633 if let (Some(d1), Some(dst)) = (src1.data.as_f64_slice(), self.data.as_f64_slice_mut()) {
1635 if let Some(d2) = src2.data.as_f64_slice() {
1636 dst.par_iter_mut()
1637 .zip(d1.par_iter())
1638 .zip(d2.par_iter())
1639 .for_each(|((out, &z1), &z2)| {
1640 if is_nd1_f64(z1) || is_nd2_f64(z2) {
1641 *out = nd_out;
1642 } else {
1643 *out = f(z1, z2);
1644 }
1645 });
1646 return Ok(());
1647 }
1648
1649 macro_rules! run_f64_rhs_typed {
1650 ($rhs:expr) => {{
1651 dst.par_iter_mut()
1652 .zip(d1.par_iter())
1653 .zip($rhs.par_iter())
1654 .for_each(|((out, &z1), &z2)| {
1655 let z2f = z2 as f64;
1656 let z2_is_nd = if nd2_is_nan { z2f.is_nan() } else { z2f == nd2 };
1657 if is_nd1_f64(z1) || z2_is_nd {
1658 *out = nd_out;
1659 } else {
1660 *out = f(z1, z2f);
1661 }
1662 });
1663 return Ok(());
1664 }};
1665 }
1666
1667 if let Some(d2) = src2.data.as_f32_slice() {
1668 dst.par_iter_mut()
1669 .zip(d1.par_iter())
1670 .zip(d2.par_iter())
1671 .for_each(|((out, &z1), &z2)| {
1672 if is_nd1_f64(z1) || is_nd2(z2) {
1673 *out = nd_out;
1674 } else {
1675 *out = f(z1, z2 as f64);
1676 }
1677 });
1678 return Ok(());
1679 }
1680 if let Some(d2) = src2.data.as_u8_slice() {
1681 run_f64_rhs_typed!(d2);
1682 }
1683 if let Some(d2) = src2.data.as_i8_slice() {
1684 run_f64_rhs_typed!(d2);
1685 }
1686 if let Some(d2) = src2.data.as_u16_slice() {
1687 run_f64_rhs_typed!(d2);
1688 }
1689 if let Some(d2) = src2.data.as_i16_slice() {
1690 run_f64_rhs_typed!(d2);
1691 }
1692 if let Some(d2) = src2.data.as_u32_slice() {
1693 run_f64_rhs_typed!(d2);
1694 }
1695 if let Some(d2) = src2.data.as_i32_slice() {
1696 run_f64_rhs_typed!(d2);
1697 }
1698 if let Some(d2) = src2.data.as_u64_slice() {
1699 run_f64_rhs_typed!(d2);
1700 }
1701 if let Some(d2) = src2.data.as_i64_slice() {
1702 run_f64_rhs_typed!(d2);
1703 }
1704 }
1705
1706 if let (Some(d1), Some(dst)) = (src1.data.as_i16_slice(), self.data.as_i16_slice_mut()) {
1709 let nd1_i16 = nd1 as i16;
1710 let nd_out_i16 = nd_out as i16;
1711 let chk1_i16 = |v: i16| v == nd1_i16;
1712
1713 macro_rules! run_i16_rhs_typed {
1714 ($rhs:expr) => {{
1715 dst.par_iter_mut()
1716 .zip(d1.par_iter())
1717 .zip($rhs.par_iter())
1718 .for_each(|((out, &z1), &z2)| {
1719 let z2f = z2 as f64;
1720 let z2_is_nd = if nd2_is_nan { z2f.is_nan() } else { z2f == nd2 };
1721 *out = if chk1_i16(z1) || z2_is_nd {
1722 nd_out_i16
1723 } else {
1724 f(z1 as f64, z2f) as i16
1725 };
1726 });
1727 return Ok(());
1728 }};
1729 }
1730
1731 if let Some(d2) = src2.data.as_f32_slice() {
1732 dst.par_iter_mut()
1733 .zip(d1.par_iter())
1734 .zip(d2.par_iter())
1735 .for_each(|((out, &z1), &z2)| {
1736 *out = if chk1_i16(z1) || is_nd2(z2) {
1737 nd_out_i16
1738 } else {
1739 f(z1 as f64, z2 as f64) as i16
1740 };
1741 });
1742 return Ok(());
1743 }
1744 if let Some(d2) = src2.data.as_f64_slice() {
1745 run_i16_rhs_typed!(d2);
1746 }
1747 if let Some(d2) = src2.data.as_u8_slice() {
1748 run_i16_rhs_typed!(d2);
1749 }
1750 if let Some(d2) = src2.data.as_i8_slice() {
1751 run_i16_rhs_typed!(d2);
1752 }
1753 if let Some(d2) = src2.data.as_u16_slice() {
1754 run_i16_rhs_typed!(d2);
1755 }
1756 if let Some(d2) = src2.data.as_i16_slice() {
1757 run_i16_rhs_typed!(d2);
1758 }
1759 if let Some(d2) = src2.data.as_u32_slice() {
1760 run_i16_rhs_typed!(d2);
1761 }
1762 if let Some(d2) = src2.data.as_i32_slice() {
1763 run_i16_rhs_typed!(d2);
1764 }
1765 if let Some(d2) = src2.data.as_u64_slice() {
1766 run_i16_rhs_typed!(d2);
1767 }
1768 if let Some(d2) = src2.data.as_i64_slice() {
1769 run_i16_rhs_typed!(d2);
1770 }
1771 }
1772
1773 if let (Some(d1), Some(dst)) = (src1.data.as_u8_slice(), self.data.as_u8_slice_mut()) {
1776 let nd1_u8 = nd1 as u8;
1777 let nd_out_u8 = nd_out as u8;
1778 let chk1_u8 = |v: u8| v == nd1_u8;
1779
1780 macro_rules! run_u8_rhs_typed {
1781 ($rhs:expr) => {{
1782 dst.par_iter_mut()
1783 .zip(d1.par_iter())
1784 .zip($rhs.par_iter())
1785 .for_each(|((out, &z1), &z2)| {
1786 let z2f = z2 as f64;
1787 let z2_is_nd = if nd2_is_nan { z2f.is_nan() } else { z2f == nd2 };
1788 *out = if chk1_u8(z1) || z2_is_nd {
1789 nd_out_u8
1790 } else {
1791 f(z1 as f64, z2f) as u8
1792 };
1793 });
1794 return Ok(());
1795 }};
1796 }
1797
1798 if let Some(d2) = src2.data.as_f32_slice() {
1799 dst.par_iter_mut()
1800 .zip(d1.par_iter())
1801 .zip(d2.par_iter())
1802 .for_each(|((out, &z1), &z2)| {
1803 *out = if chk1_u8(z1) || is_nd2(z2) {
1804 nd_out_u8
1805 } else {
1806 f(z1 as f64, z2 as f64) as u8
1807 };
1808 });
1809 return Ok(());
1810 }
1811 if let Some(d2) = src2.data.as_f64_slice() {
1812 run_u8_rhs_typed!(d2);
1813 }
1814 if let Some(d2) = src2.data.as_u8_slice() {
1815 run_u8_rhs_typed!(d2);
1816 }
1817 if let Some(d2) = src2.data.as_i8_slice() {
1818 run_u8_rhs_typed!(d2);
1819 }
1820 if let Some(d2) = src2.data.as_u16_slice() {
1821 run_u8_rhs_typed!(d2);
1822 }
1823 if let Some(d2) = src2.data.as_i16_slice() {
1824 run_u8_rhs_typed!(d2);
1825 }
1826 if let Some(d2) = src2.data.as_u32_slice() {
1827 run_u8_rhs_typed!(d2);
1828 }
1829 if let Some(d2) = src2.data.as_i32_slice() {
1830 run_u8_rhs_typed!(d2);
1831 }
1832 if let Some(d2) = src2.data.as_u64_slice() {
1833 run_u8_rhs_typed!(d2);
1834 }
1835 if let Some(d2) = src2.data.as_i64_slice() {
1836 run_u8_rhs_typed!(d2);
1837 }
1838 }
1839
1840 self.data.par_fill_with(|i| {
1845 let z1 = src1.data.get_f64(i);
1846 let z2 = src2.data.get_f64(i);
1847 if is_nd1_f64(z1) || is_nd2_f64(z2) {
1848 nd_out
1849 } else {
1850 f(z1, z2)
1851 }
1852 });
1853
1854 Ok(())
1855 }
1856
1857 pub fn apply_scalar_add(&mut self, src: &Raster, scalar: f64) -> Result<()> {
1865 self.apply_unary_math_from(|z| z + scalar, src)
1866 }
1867
1868 pub fn apply_scalar_sub(&mut self, src: &Raster, scalar: f64) -> Result<()> {
1876 self.apply_unary_math_from(|z| z - scalar, src)
1877 }
1878
1879 #[inline]
1884 pub fn index(&self, band: isize, row: isize, col: isize) -> Option<usize> {
1885 if band < 0
1886 || row < 0
1887 || col < 0
1888 || band >= self.bands as isize
1889 || row >= self.rows as isize
1890 || col >= self.cols as isize
1891 {
1892 return None;
1893 }
1894 let band = band as usize;
1895 let row = row as usize;
1896 let col = col as usize;
1897 let band_stride = self.rows * self.cols;
1898 Some(band * band_stride + row * self.cols + col)
1899 }
1900
1901 #[inline]
1906 pub fn get(&self, band: isize, row: isize, col: isize) -> f64 {
1907 self.get_raw(band, row, col).unwrap_or(self.nodata)
1908 }
1909
1910 #[inline]
1915 pub fn get_opt(&self, band: isize, row: isize, col: isize) -> Option<f64> {
1916 let idx = self.index(band, row, col)?;
1917 let v = self.data.get_f64(idx);
1918 if self.is_nodata(v) {
1919 None
1920 } else {
1921 Some(v)
1922 }
1923 }
1924
1925 #[inline]
1928 pub fn get_raw(&self, band: isize, row: isize, col: isize) -> Option<f64> {
1929 let idx = self.index(band, row, col)?;
1930 Some(self.data.get_f64(idx))
1931 }
1932
1933 #[inline]
1938 pub fn set(&mut self, band: isize, row: isize, col: isize, value: f64) -> Result<()> {
1939 if band < 0
1940 || row < 0
1941 || col < 0
1942 || band >= self.bands as isize
1943 || row >= self.rows as isize
1944 || col >= self.cols as isize
1945 {
1946 return Err(RasterError::OutOfBounds {
1947 band,
1948 col,
1949 row,
1950 bands: self.bands,
1951 cols: self.cols,
1952 rows: self.rows,
1953 });
1954 }
1955 let idx = self.index(band, row, col).expect("set bounds prechecked");
1956 self.data.set_f64(idx, value);
1957 Ok(())
1958 }
1959
1960 #[inline]
1962 pub fn set_unchecked(&mut self, band: isize, row: isize, col: isize, value: f64) {
1963 let idx = self
1964 .index(band, row, col)
1965 .expect("set_unchecked requires in-bounds coordinates");
1966 self.data.set_f64(idx, value);
1967 }
1968
1969 #[inline]
1971 pub fn is_nodata(&self, v: f64) -> bool {
1972 if self.nodata.is_nan() {
1973 v.is_nan()
1974 } else {
1975 (v - self.nodata).abs() < 1e-10 * self.nodata.abs().max(1.0)
1976 }
1977 }
1978
1979 #[inline]
1983 pub fn y_max(&self) -> f64 {
1984 self.y_min + self.rows as f64 * self.cell_size_y
1985 }
1986
1987 #[inline]
1989 pub fn x_max(&self) -> f64 {
1990 self.x_min + self.cols as f64 * self.cell_size_x
1991 }
1992
1993 pub fn extent(&self) -> Extent {
1995 Extent {
1996 x_min: self.x_min,
1997 y_min: self.y_min,
1998 x_max: self.x_max(),
1999 y_max: self.y_max(),
2000 }
2001 }
2002
2003 #[inline]
2005 pub fn col_center_x(&self, col: isize) -> f64 {
2006 self.x_min + (col as f64 + 0.5) * self.cell_size_x
2007 }
2008
2009 #[inline]
2011 pub fn row_center_y(&self, row: isize) -> f64 {
2012 self.y_max() - (row as f64 + 0.5) * self.cell_size_y
2013 }
2014
2015 pub fn world_to_pixel(&self, x: f64, y: f64) -> Option<(isize, isize)> {
2018 if x < self.x_min || x >= self.x_max() || y < self.y_min || y >= self.y_max() {
2019 return None;
2020 }
2021 let col = ((x - self.x_min) / self.cell_size_x).floor() as isize;
2022 let row = ((self.y_max() - y) / self.cell_size_y).floor() as isize;
2023 let col = col.min(self.cols as isize - 1);
2024 let row = row.min(self.rows as isize - 1);
2025 Some((col, row))
2026 }
2027
2028 pub fn assign_crs_epsg(&mut self, epsg: u32) {
2033 self.crs = CrsInfo {
2034 epsg: Some(epsg),
2035 wkt: None,
2036 proj4: None,
2037 };
2038 }
2039
2040 pub fn assign_crs_wkt(&mut self, wkt: &str) {
2045 self.crs = CrsInfo {
2046 epsg: None,
2047 wkt: Some(wkt.to_string()),
2048 proj4: None,
2049 };
2050 }
2051
2052 pub fn reproject_to_epsg(&self, dst_epsg: u32, resample: ResampleMethod) -> Result<Raster> {
2066 self.reproject_with_options(&ReprojectOptions::new(dst_epsg, resample))
2067 }
2068
2069 pub fn reproject_with_options(&self, options: &ReprojectOptions) -> Result<Raster> {
2071 let src_crs = self.source_crs_for_reprojection()?;
2072 let dst_crs = Crs::from_epsg(options.dst_epsg).map_err(|e| {
2073 RasterError::Other(format!(
2074 "destination EPSG {} is not supported: {e}",
2075 options.dst_epsg
2076 ))
2077 })?;
2078
2079 self.reproject_internal(&src_crs, &dst_crs, options, None)
2080 }
2081
2082 pub fn reproject_with_options_and_progress<F>(
2085 &self,
2086 options: &ReprojectOptions,
2087 progress: F,
2088 ) -> Result<Raster>
2089 where
2090 F: Fn(f64) + Send + Sync,
2091 {
2092 let src_crs = self.source_crs_for_reprojection()?;
2093 let dst_crs = Crs::from_epsg(options.dst_epsg).map_err(|e| {
2094 RasterError::Other(format!(
2095 "destination EPSG {} is not supported: {e}",
2096 options.dst_epsg
2097 ))
2098 })?;
2099
2100 self.reproject_internal(&src_crs, &dst_crs, options, Some(&progress))
2101 }
2102
2103 pub fn reproject_with_crs(
2111 &self,
2112 src_crs: &Crs,
2113 dst_crs: &Crs,
2114 options: &ReprojectOptions,
2115 ) -> Result<Raster> {
2116 self.reproject_internal(src_crs, dst_crs, options, None)
2117 }
2118
2119 pub fn reproject_with_crs_and_progress<F>(
2122 &self,
2123 src_crs: &Crs,
2124 dst_crs: &Crs,
2125 options: &ReprojectOptions,
2126 progress: F,
2127 ) -> Result<Raster>
2128 where
2129 F: Fn(f64) + Send + Sync,
2130 {
2131 self.reproject_internal(src_crs, dst_crs, options, Some(&progress))
2132 }
2133
2134 pub fn reproject_to_epsg_nearest(&self, dst_epsg: u32) -> Result<Raster> {
2136 self.reproject_to_epsg(dst_epsg, ResampleMethod::Nearest)
2137 }
2138
2139 pub fn reproject_to_epsg_bilinear(&self, dst_epsg: u32) -> Result<Raster> {
2141 self.reproject_to_epsg(dst_epsg, ResampleMethod::Bilinear)
2142 }
2143
2144 pub fn reproject_to_epsg_cubic(&self, dst_epsg: u32) -> Result<Raster> {
2146 self.reproject_to_epsg(dst_epsg, ResampleMethod::Cubic)
2147 }
2148
2149 pub fn reproject_to_epsg_lanczos(&self, dst_epsg: u32) -> Result<Raster> {
2151 self.reproject_to_epsg(dst_epsg, ResampleMethod::Lanczos)
2152 }
2153
2154 pub fn reproject_to_epsg_average(&self, dst_epsg: u32) -> Result<Raster> {
2156 self.reproject_to_epsg(dst_epsg, ResampleMethod::Average)
2157 }
2158
2159 pub fn reproject_to_epsg_min(&self, dst_epsg: u32) -> Result<Raster> {
2161 self.reproject_to_epsg(dst_epsg, ResampleMethod::Min)
2162 }
2163
2164 pub fn reproject_to_epsg_max(&self, dst_epsg: u32) -> Result<Raster> {
2166 self.reproject_to_epsg(dst_epsg, ResampleMethod::Max)
2167 }
2168
2169 pub fn reproject_to_epsg_mode(&self, dst_epsg: u32) -> Result<Raster> {
2171 self.reproject_to_epsg(dst_epsg, ResampleMethod::Mode)
2172 }
2173
2174 pub fn reproject_to_epsg_median(&self, dst_epsg: u32) -> Result<Raster> {
2176 self.reproject_to_epsg(dst_epsg, ResampleMethod::Median)
2177 }
2178
2179 pub fn reproject_to_epsg_stddev(&self, dst_epsg: u32) -> Result<Raster> {
2181 self.reproject_to_epsg(dst_epsg, ResampleMethod::StdDev)
2182 }
2183
2184 pub fn reproject_to_match_grid(
2193 &self,
2194 target_grid: &Raster,
2195 resample: ResampleMethod,
2196 ) -> Result<Raster> {
2197 let dst_epsg = target_grid.crs.epsg.ok_or_else(|| {
2198 RasterError::Other(
2199 "reproject_to_match_grid requires target grid CRS EPSG in target_grid.crs.epsg"
2200 .to_string(),
2201 )
2202 })?;
2203
2204 let opts = ReprojectOptions::new(dst_epsg, resample)
2205 .with_size(target_grid.cols, target_grid.rows)
2206 .with_extent(target_grid.extent());
2207
2208 self.reproject_with_options(&opts)
2209 }
2210
2211 pub fn reproject_to_match_grid_and_progress<F>(
2214 &self,
2215 target_grid: &Raster,
2216 resample: ResampleMethod,
2217 progress: F,
2218 ) -> Result<Raster>
2219 where
2220 F: Fn(f64) + Send + Sync,
2221 {
2222 let dst_epsg = target_grid.crs.epsg.ok_or_else(|| {
2223 RasterError::Other(
2224 "reproject_to_match_grid requires target grid CRS EPSG in target_grid.crs.epsg"
2225 .to_string(),
2226 )
2227 })?;
2228
2229 let opts = ReprojectOptions::new(dst_epsg, resample)
2230 .with_size(target_grid.cols, target_grid.rows)
2231 .with_extent(target_grid.extent());
2232
2233 self.reproject_with_options_and_progress(&opts, progress)
2234 }
2235
2236 pub fn reproject_to_match_resolution(
2245 &self,
2246 reference_grid: &Raster,
2247 resample: ResampleMethod,
2248 ) -> Result<Raster> {
2249 let dst_epsg = reference_grid.crs.epsg.ok_or_else(|| {
2250 RasterError::Other(
2251 "reproject_to_match_resolution requires reference grid CRS EPSG in reference_grid.crs.epsg"
2252 .to_string(),
2253 )
2254 })?;
2255
2256 let opts = ReprojectOptions::new(dst_epsg, resample)
2257 .with_resolution(reference_grid.cell_size_x, reference_grid.cell_size_y)
2258 .with_snap_origin(reference_grid.x_min, reference_grid.y_min);
2259
2260 self.reproject_with_options(&opts)
2261 }
2262
2263 pub fn reproject_to_match_resolution_and_progress<F>(
2266 &self,
2267 reference_grid: &Raster,
2268 resample: ResampleMethod,
2269 progress: F,
2270 ) -> Result<Raster>
2271 where
2272 F: Fn(f64) + Send + Sync,
2273 {
2274 let dst_epsg = reference_grid.crs.epsg.ok_or_else(|| {
2275 RasterError::Other(
2276 "reproject_to_match_resolution requires reference grid CRS EPSG in reference_grid.crs.epsg"
2277 .to_string(),
2278 )
2279 })?;
2280
2281 let opts = ReprojectOptions::new(dst_epsg, resample)
2282 .with_resolution(reference_grid.cell_size_x, reference_grid.cell_size_y)
2283 .with_snap_origin(reference_grid.x_min, reference_grid.y_min);
2284
2285 self.reproject_with_options_and_progress(&opts, progress)
2286 }
2287
2288 pub fn reproject_to_match_resolution_in_epsg(
2299 &self,
2300 dst_epsg: u32,
2301 reference_grid: &Raster,
2302 resample: ResampleMethod,
2303 ) -> Result<Raster> {
2304 let reference_epsg = reference_grid.crs.epsg.ok_or_else(|| {
2305 RasterError::Other(
2306 "reproject_to_match_resolution_in_epsg requires reference grid CRS EPSG in reference_grid.crs.epsg"
2307 .to_string(),
2308 )
2309 })?;
2310
2311 let (snap_x, snap_y, x_res, y_res) = if reference_epsg == dst_epsg {
2312 (
2313 reference_grid.x_min,
2314 reference_grid.y_min,
2315 reference_grid.cell_size_x,
2316 reference_grid.cell_size_y,
2317 )
2318 } else {
2319 let ref_crs = Crs::from_epsg(reference_epsg).map_err(|e| {
2320 RasterError::Other(format!(
2321 "reference EPSG {reference_epsg} is not supported: {e}"
2322 ))
2323 })?;
2324 let dst_crs = Crs::from_epsg(dst_epsg).map_err(|e| {
2325 RasterError::Other(format!("destination EPSG {dst_epsg} is not supported: {e}"))
2326 })?;
2327
2328 let ox = reference_grid.x_min;
2329 let oy = reference_grid.y_min;
2330 let (sx, sy) = ref_crs.transform_to(ox, oy, &dst_crs).map_err(|e| {
2331 RasterError::Other(format!(
2332 "failed to transform reference snap origin to EPSG:{dst_epsg}: {e}"
2333 ))
2334 })?;
2335 let (sx_dx, sy_dx) = ref_crs
2336 .transform_to(ox + reference_grid.cell_size_x, oy, &dst_crs)
2337 .map_err(|e| {
2338 RasterError::Other(format!(
2339 "failed to transform reference X-step to EPSG:{dst_epsg}: {e}"
2340 ))
2341 })?;
2342 let (sx_dy, sy_dy) = ref_crs
2343 .transform_to(ox, oy + reference_grid.cell_size_y, &dst_crs)
2344 .map_err(|e| {
2345 RasterError::Other(format!(
2346 "failed to transform reference Y-step to EPSG:{dst_epsg}: {e}"
2347 ))
2348 })?;
2349
2350 let rx = (sx_dx - sx).hypot(sy_dx - sy);
2351 let ry = (sx_dy - sx).hypot(sy_dy - sy);
2352 if !rx.is_finite() || !ry.is_finite() || rx <= 0.0 || ry <= 0.0 {
2353 return Err(RasterError::Other(
2354 "invalid transformed reference resolution while matching destination EPSG"
2355 .to_string(),
2356 ));
2357 }
2358 (sx, sy, rx, ry)
2359 };
2360
2361 let opts = ReprojectOptions::new(dst_epsg, resample)
2362 .with_resolution(x_res, y_res)
2363 .with_snap_origin(snap_x, snap_y);
2364
2365 self.reproject_with_options(&opts)
2366 }
2367
2368 pub fn reproject_to_match_resolution_in_epsg_and_progress<F>(
2371 &self,
2372 dst_epsg: u32,
2373 reference_grid: &Raster,
2374 resample: ResampleMethod,
2375 progress: F,
2376 ) -> Result<Raster>
2377 where
2378 F: Fn(f64) + Send + Sync,
2379 {
2380 let reference_epsg = reference_grid.crs.epsg.ok_or_else(|| {
2381 RasterError::Other(
2382 "reproject_to_match_resolution_in_epsg requires reference grid CRS EPSG in reference_grid.crs.epsg"
2383 .to_string(),
2384 )
2385 })?;
2386
2387 let (snap_x, snap_y, x_res, y_res) = if reference_epsg == dst_epsg {
2388 (
2389 reference_grid.x_min,
2390 reference_grid.y_min,
2391 reference_grid.cell_size_x,
2392 reference_grid.cell_size_y,
2393 )
2394 } else {
2395 let ref_crs = Crs::from_epsg(reference_epsg).map_err(|e| {
2396 RasterError::Other(format!(
2397 "reference EPSG {reference_epsg} is not supported: {e}"
2398 ))
2399 })?;
2400 let dst_crs = Crs::from_epsg(dst_epsg).map_err(|e| {
2401 RasterError::Other(format!("destination EPSG {dst_epsg} is not supported: {e}"))
2402 })?;
2403
2404 let ox = reference_grid.x_min;
2405 let oy = reference_grid.y_min;
2406 let (sx, sy) = ref_crs.transform_to(ox, oy, &dst_crs).map_err(|e| {
2407 RasterError::Other(format!(
2408 "failed to transform reference snap origin to EPSG:{dst_epsg}: {e}"
2409 ))
2410 })?;
2411 let (sx_dx, sy_dx) = ref_crs
2412 .transform_to(ox + reference_grid.cell_size_x, oy, &dst_crs)
2413 .map_err(|e| {
2414 RasterError::Other(format!(
2415 "failed to transform reference X-step to EPSG:{dst_epsg}: {e}"
2416 ))
2417 })?;
2418 let (sx_dy, sy_dy) = ref_crs
2419 .transform_to(ox, oy + reference_grid.cell_size_y, &dst_crs)
2420 .map_err(|e| {
2421 RasterError::Other(format!(
2422 "failed to transform reference Y-step to EPSG:{dst_epsg}: {e}"
2423 ))
2424 })?;
2425
2426 let rx = (sx_dx - sx).hypot(sy_dx - sy);
2427 let ry = (sx_dy - sx).hypot(sy_dy - sy);
2428 if !rx.is_finite() || !ry.is_finite() || rx <= 0.0 || ry <= 0.0 {
2429 return Err(RasterError::Other(
2430 "invalid transformed reference resolution while matching destination EPSG"
2431 .to_string(),
2432 ));
2433 }
2434 (sx, sy, rx, ry)
2435 };
2436
2437 let opts = ReprojectOptions::new(dst_epsg, resample)
2438 .with_resolution(x_res, y_res)
2439 .with_snap_origin(snap_x, snap_y);
2440
2441 self.reproject_with_options_and_progress(&opts, progress)
2442 }
2443
2444 fn reproject_internal(
2445 &self,
2446 src_crs: &Crs,
2447 dst_crs: &Crs,
2448 options: &ReprojectOptions,
2449 progress: Option<&(dyn Fn(f64) + Send + Sync)>,
2450 ) -> Result<Raster> {
2451 maybe_warn_area_of_use_mismatch(
2452 src_crs,
2453 dst_crs,
2454 self.extent(),
2455 options.warn_on_area_of_use_mismatch,
2456 );
2457
2458 let src_extent = self.extent();
2459 let samples_per_edge = (self.cols.max(self.rows) / 32).clamp(8, 128);
2460 let base_extent = transformed_extent_from_boundary_samples(
2461 src_crs,
2462 dst_crs,
2463 src_extent,
2464 samples_per_edge,
2465 options.dst_epsg,
2466 options.antimeridian_policy,
2467 &options.epoch_transform,
2468 )?;
2469 let out_extent = options.extent.unwrap_or(base_extent);
2470 let width = out_extent.x_max - out_extent.x_min;
2471 let height = out_extent.y_max - out_extent.y_min;
2472
2473 if !out_extent.x_min.is_finite()
2474 || !out_extent.x_max.is_finite()
2475 || !out_extent.y_min.is_finite()
2476 || !out_extent.y_max.is_finite()
2477 || width <= 0.0
2478 || height <= 0.0
2479 {
2480 return Err(RasterError::CorruptData(
2481 "invalid transformed extent produced during reprojection".to_string(),
2482 ));
2483 }
2484
2485 let x_res = options.x_res.map(f64::abs);
2486 let y_res = options.y_res.map(f64::abs);
2487 if x_res.is_some_and(|v| !v.is_finite() || v <= 0.0)
2488 || y_res.is_some_and(|v| !v.is_finite() || v <= 0.0)
2489 {
2490 return Err(RasterError::CorruptData(
2491 "invalid reprojection resolution (x_res/y_res must be positive finite values)"
2492 .to_string(),
2493 ));
2494 }
2495
2496 let mut x_min = out_extent.x_min;
2497 let mut x_max = out_extent.x_max;
2498 let mut y_min = out_extent.y_min;
2499 let mut y_max = out_extent.y_max;
2500
2501 let out_cols = match options.cols {
2502 Some(cols) => cols,
2503 None => match x_res {
2504 Some(rx) => {
2505 if let Some(sx) = options.snap_x {
2506 match options.grid_size_policy {
2507 GridSizePolicy::Expand => {
2508 x_min = snap_down_to_origin(x_min, sx, rx);
2509 x_max = snap_up_to_origin(x_max, sx, rx);
2510 }
2511 GridSizePolicy::FitInside => {
2512 x_min = snap_up_to_origin(x_min, sx, rx);
2513 x_max = snap_down_to_origin(x_max, sx, rx);
2514 }
2515 }
2516 }
2517 let span = (x_max - x_min).max(0.0);
2518 let cols = match options.grid_size_policy {
2519 GridSizePolicy::Expand => (span / rx).ceil().max(1.0) as usize,
2520 GridSizePolicy::FitInside => (span / rx).floor().max(1.0) as usize,
2521 };
2522 x_max = x_min + cols as f64 * rx;
2523 cols
2524 }
2525 None => self.cols,
2526 },
2527 };
2528 let out_rows = match options.rows {
2529 Some(rows) => rows,
2530 None => match y_res {
2531 Some(ry) => {
2532 if let Some(sy) = options.snap_y {
2533 match options.grid_size_policy {
2534 GridSizePolicy::Expand => {
2535 y_min = snap_down_to_origin(y_min, sy, ry);
2536 y_max = snap_up_to_origin(y_max, sy, ry);
2537 }
2538 GridSizePolicy::FitInside => {
2539 y_min = snap_up_to_origin(y_min, sy, ry);
2540 y_max = snap_down_to_origin(y_max, sy, ry);
2541 }
2542 }
2543 }
2544 let span = (y_max - y_min).max(0.0);
2545 let rows = match options.grid_size_policy {
2546 GridSizePolicy::Expand => (span / ry).ceil().max(1.0) as usize,
2547 GridSizePolicy::FitInside => (span / ry).floor().max(1.0) as usize,
2548 };
2549 y_max = y_min + rows as f64 * ry;
2550 rows
2551 }
2552 None => self.rows,
2553 },
2554 };
2555
2556 let out_extent = Extent {
2557 x_min,
2558 y_min,
2559 x_max,
2560 y_max,
2561 };
2562
2563 if out_cols == 0 || out_rows == 0 {
2564 return Err(RasterError::InvalidDimensions {
2565 cols: out_cols,
2566 rows: out_rows,
2567 });
2568 }
2569
2570 let cfg = RasterConfig {
2571 cols: out_cols,
2572 rows: out_rows,
2573 bands: self.bands,
2574 x_min: out_extent.x_min,
2575 y_min: out_extent.y_min,
2576 cell_size: (out_extent.x_max - out_extent.x_min) / out_cols as f64,
2577 cell_size_y: Some((out_extent.y_max - out_extent.y_min) / out_rows as f64),
2578 nodata: self.nodata,
2579 data_type: self.data_type,
2580 crs: CrsInfo::from_epsg(options.dst_epsg),
2581 metadata: self.metadata.clone(),
2582 };
2583 let mut out = Raster::new(cfg);
2584
2585 let out_y_max = out.y_max();
2586 let footprint_ring =
2587 if options.destination_footprint == DestinationFootprint::SourceBoundary {
2588 let ring = transformed_boundary_ring_samples(
2589 src_crs,
2590 dst_crs,
2591 src_extent,
2592 samples_per_edge,
2593 options.dst_epsg,
2594 options.antimeridian_policy,
2595 &options.epoch_transform,
2596 )?;
2597 if ring.len() >= 3 {
2598 Some(ring)
2599 } else {
2600 None
2601 }
2602 } else {
2603 None
2604 };
2605
2606 let total_rows = out.rows;
2607 let completed_rows = AtomicUsize::new(0);
2608 let rows_data: Vec<Vec<Option<f64>>> = (0..out.rows as isize)
2609 .into_par_iter()
2610 .map(|row| {
2611 let mut row_values = vec![None; out.cols * out.bands];
2612 let y = out_y_max - (row as f64 + 0.5) * out.cell_size_y;
2613
2614 let mut batch_coords: Vec<(f64, f64)> = Vec::with_capacity(out.cols);
2618 let mut batch_cols: Vec<usize> = Vec::with_capacity(out.cols);
2619 for col in 0..out.cols as isize {
2620 let x = out.x_min + (col as f64 + 0.5) * out.cell_size_x;
2621 if let Some(ring) = &footprint_ring {
2622 if !point_in_polygon(x, y, ring) {
2623 continue;
2624 }
2625 }
2626 batch_coords.push((x, y));
2627 batch_cols.push(col as usize);
2628 }
2629
2630 let epoch_routing_requested = options
2631 .epoch_transform
2632 .coordinate_epoch_decimal_year
2633 .is_some()
2634 || options
2635 .epoch_transform
2636 .source_reference_epoch_decimal_year
2637 .is_some()
2638 || options
2639 .epoch_transform
2640 .target_reference_epoch_decimal_year
2641 .is_some()
2642 || options.epoch_transform.operation_code.is_some()
2643 || !options.epoch_transform.prefer_official_operation
2644 || matches!(
2645 options.epoch_transform.epoch_policy,
2646 EpochPolicy::AllowStaticFallback
2647 );
2648
2649 if !epoch_routing_requested {
2650 let errors = dst_crs.transform_to_batch(&mut batch_coords, src_crs);
2654
2655 for (i, &col) in batch_cols.iter().enumerate() {
2656 if errors[i].is_some() {
2657 continue;
2658 }
2659 let (sx, sy) = batch_coords[i];
2660 for band in 0..out.bands as isize {
2661 if let Some(v) = self.sample_world(
2662 band,
2663 sx,
2664 sy,
2665 options.resample,
2666 options.nodata_policy,
2667 ) {
2668 row_values[band as usize * out.cols + col] = Some(v);
2669 }
2670 }
2671 }
2672 } else {
2673 for (i, &col) in batch_cols.iter().enumerate() {
2674 let Ok((sx, sy)) = transform_xy_with_epoch_options(
2675 src_crs,
2676 dst_crs,
2677 batch_coords[i].0,
2678 batch_coords[i].1,
2679 &options.epoch_transform,
2680 ) else {
2681 continue;
2682 };
2683 for band in 0..out.bands as isize {
2684 if let Some(v) = self.sample_world(
2685 band,
2686 sx,
2687 sy,
2688 options.resample,
2689 options.nodata_policy,
2690 ) {
2691 row_values[band as usize * out.cols + col] = Some(v);
2692 }
2693 }
2694 }
2695 }
2696
2697 if let Some(progress_cb) = progress {
2698 let done = completed_rows.fetch_add(1, Ordering::Relaxed) + 1;
2699 progress_cb(done as f64 / total_rows as f64);
2700 }
2701
2702 row_values
2703 })
2704 .collect();
2705
2706 for (row, row_values) in rows_data.into_iter().enumerate() {
2707 let row = row as isize;
2708 for band in 0..out.bands {
2709 for col in 0..out.cols {
2710 if let Some(v) = row_values[band * out.cols + col] {
2711 out.set_unchecked(band as isize, row, col as isize, v);
2712 }
2713 }
2714 }
2715 }
2716
2717 if let Some(progress_cb) = progress {
2718 progress_cb(1.0);
2719 }
2720
2721 Ok(out)
2722 }
2723
2724 fn source_crs_for_reprojection(&self) -> Result<Crs> {
2725 if let Some(src_epsg) = self.crs.epsg {
2726 return Crs::from_epsg(src_epsg).map_err(|e| {
2727 RasterError::Other(format!("source EPSG {src_epsg} is not supported: {e}"))
2728 });
2729 }
2730
2731 if let Some(wkt) = self.crs.wkt.as_deref() {
2732 let trimmed = wkt.trim();
2733 if !trimmed.is_empty() {
2734 return wbprojection::from_wkt(trimmed).map_err(|e| {
2735 RasterError::Other(format!("source CRS WKT is not supported: {e}"))
2736 });
2737 }
2738 }
2739
2740 if let Some(proj) = self.crs.proj4.as_deref() {
2741 let trimmed = proj.trim();
2742 if !trimmed.is_empty() {
2743 return from_proj_string(trimmed).map_err(|e| {
2744 RasterError::Other(format!("source CRS PROJ string is not supported: {e}"))
2745 });
2746 }
2747 }
2748
2749 Err(RasterError::Other(
2750 "reproject_to_epsg requires source CRS metadata (EPSG, WKT, or PROJ string)"
2751 .to_string(),
2752 ))
2753 }
2754
2755 pub fn sample_world(
2757 &self,
2758 band: isize,
2759 x: f64,
2760 y: f64,
2761 method: ResampleMethod,
2762 nodata_policy: NodataPolicy,
2763 ) -> Option<f64> {
2764 let col_f = (x - self.x_min) / self.cell_size_x - 0.5;
2765 let row_f = (self.y_max() - y) / self.cell_size_y - 0.5;
2766 match method {
2767 ResampleMethod::Nearest => self.sample_nearest_pixel(band, col_f, row_f),
2768 ResampleMethod::Bilinear => match nodata_policy {
2769 NodataPolicy::Strict => self.sample_bilinear_strict_pixel(band, col_f, row_f),
2770 NodataPolicy::PartialKernel => {
2771 self.sample_bilinear_partial_pixel(band, col_f, row_f)
2772 }
2773 NodataPolicy::Fill => self
2774 .sample_bilinear_strict_pixel(band, col_f, row_f)
2775 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
2776 },
2777 ResampleMethod::Cubic => match nodata_policy {
2778 NodataPolicy::Strict => self.sample_cubic_strict_pixel(band, col_f, row_f),
2779 NodataPolicy::PartialKernel => self.sample_cubic_partial_pixel(band, col_f, row_f),
2780 NodataPolicy::Fill => self
2781 .sample_cubic_strict_pixel(band, col_f, row_f)
2782 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
2783 },
2784 ResampleMethod::Lanczos => match nodata_policy {
2785 NodataPolicy::Strict => self.sample_lanczos_strict_pixel(band, col_f, row_f),
2786 NodataPolicy::PartialKernel => {
2787 self.sample_lanczos_partial_pixel(band, col_f, row_f)
2788 }
2789 NodataPolicy::Fill => self
2790 .sample_lanczos_strict_pixel(band, col_f, row_f)
2791 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
2792 },
2793 ResampleMethod::Average => {
2794 self.sample_window_stat_pixel(band, col_f, row_f, WindowStat::Mean, nodata_policy)
2795 }
2796 ResampleMethod::Min => {
2797 self.sample_window_stat_pixel(band, col_f, row_f, WindowStat::Min, nodata_policy)
2798 }
2799 ResampleMethod::Max => {
2800 self.sample_window_stat_pixel(band, col_f, row_f, WindowStat::Max, nodata_policy)
2801 }
2802 ResampleMethod::Mode => {
2803 self.sample_window_stat_pixel(band, col_f, row_f, WindowStat::Mode, nodata_policy)
2804 }
2805 ResampleMethod::Median => {
2806 self.sample_window_stat_pixel(band, col_f, row_f, WindowStat::Median, nodata_policy)
2807 }
2808 ResampleMethod::StdDev => {
2809 self.sample_window_stat_pixel(band, col_f, row_f, WindowStat::StdDev, nodata_policy)
2810 }
2811 }
2812 }
2813
2814 fn sample_window_stat_pixel(
2815 &self,
2816 band: isize,
2817 col_f: f64,
2818 row_f: f64,
2819 stat: WindowStat,
2820 nodata_policy: NodataPolicy,
2821 ) -> Option<f64> {
2822 if !col_f.is_finite() || !row_f.is_finite() {
2823 return None;
2824 }
2825
2826 let center_col = col_f.round() as isize;
2827 let center_row = row_f.round() as isize;
2828 let mut values = Vec::with_capacity(9);
2829 let mut valid_count = 0usize;
2830
2831 for dy in -1..=1 {
2832 for dx in -1..=1 {
2833 let c = center_col + dx;
2834 let r = center_row + dy;
2835 if c < 0 || r < 0 || c >= self.cols as isize || r >= self.rows as isize {
2836 continue;
2837 }
2838 if let Some(v) = self.get_opt(band, r, c) {
2839 values.push(v);
2840 valid_count += 1;
2841 }
2842 }
2843 }
2844
2845 match nodata_policy {
2846 NodataPolicy::Strict if valid_count < 9 => None,
2847 NodataPolicy::PartialKernel | NodataPolicy::Strict => {
2848 reduce_window_values(&values, stat)
2849 }
2850 NodataPolicy::Fill => reduce_window_values(&values, stat)
2851 .or_else(|| self.sample_nearest_pixel(band, col_f, row_f)),
2852 }
2853 }
2854
2855 fn sample_nearest_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2856 if !col_f.is_finite() || !row_f.is_finite() {
2857 return None;
2858 }
2859 let col = col_f.round() as isize;
2860 let row = row_f.round() as isize;
2861 self.get_opt(band, row, col)
2862 }
2863
2864 fn sample_bilinear_strict_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2865 if !col_f.is_finite() || !row_f.is_finite() {
2866 return None;
2867 }
2868 let c0 = col_f.floor() as isize;
2869 let r0 = row_f.floor() as isize;
2870 let c1 = c0 + 1;
2871 let r1 = r0 + 1;
2872 if c0 < 0 || r0 < 0 || c1 >= self.cols as isize || r1 >= self.rows as isize {
2873 return None;
2874 }
2875
2876 let tx = col_f - c0 as f64;
2877 let ty = row_f - r0 as f64;
2878
2879 if let Some(values) = self.data_f64() {
2880 return self.sample_bilinear_strict_simd_f64(values, band, r0, c0, tx, ty);
2881 }
2882 if let Some(values) = self.data_f32() {
2883 return self.sample_bilinear_strict_simd_f32(values, band, r0, c0, tx, ty);
2884 }
2885
2886 let q00 = self.get_opt(band, r0, c0)?;
2887 let q10 = self.get_opt(band, r0, c1)?;
2888 let q01 = self.get_opt(band, r1, c0)?;
2889 let q11 = self.get_opt(band, r1, c1)?;
2890
2891 let a = q00 * (1.0 - tx) + q10 * tx;
2892 let b = q01 * (1.0 - tx) + q11 * tx;
2893 Some(a * (1.0 - ty) + b * ty)
2894 }
2895
2896 #[inline]
2897 fn sample_bilinear_strict_simd_f64(
2898 &self,
2899 values: &[f64],
2900 band: isize,
2901 r0: isize,
2902 c0: isize,
2903 tx: f64,
2904 ty: f64,
2905 ) -> Option<f64> {
2906 let band_stride = self.rows * self.cols;
2907 let base = band as usize * band_stride + r0 as usize * self.cols + c0 as usize;
2908 let q00 = values[base];
2909 let q10 = values[base + 1];
2910 let q01 = values[base + self.cols];
2911 let q11 = values[base + self.cols + 1];
2912
2913 if self.is_nodata(q00) || self.is_nodata(q10) || self.is_nodata(q01) || self.is_nodata(q11)
2914 {
2915 return None;
2916 }
2917
2918 let weights = f64x4::new([
2919 (1.0 - tx) * (1.0 - ty),
2920 tx * (1.0 - ty),
2921 (1.0 - tx) * ty,
2922 tx * ty,
2923 ]);
2924 let vals = f64x4::new([q00, q10, q01, q11]);
2925 let weighted = <[f64; 4]>::from(vals * weights);
2926 Some(weighted.into_iter().sum())
2927 }
2928
2929 #[inline]
2930 fn sample_bilinear_strict_simd_f32(
2931 &self,
2932 values: &[f32],
2933 band: isize,
2934 r0: isize,
2935 c0: isize,
2936 tx: f64,
2937 ty: f64,
2938 ) -> Option<f64> {
2939 let band_stride = self.rows * self.cols;
2940 let base = band as usize * band_stride + r0 as usize * self.cols + c0 as usize;
2941 let q00 = values[base] as f64;
2942 let q10 = values[base + 1] as f64;
2943 let q01 = values[base + self.cols] as f64;
2944 let q11 = values[base + self.cols + 1] as f64;
2945
2946 if self.is_nodata(q00) || self.is_nodata(q10) || self.is_nodata(q01) || self.is_nodata(q11)
2947 {
2948 return None;
2949 }
2950
2951 let weights = f64x4::new([
2952 (1.0 - tx) * (1.0 - ty),
2953 tx * (1.0 - ty),
2954 (1.0 - tx) * ty,
2955 tx * ty,
2956 ]);
2957 let vals = f64x4::new([q00, q10, q01, q11]);
2958 let weighted = <[f64; 4]>::from(vals * weights);
2959 Some(weighted.into_iter().sum())
2960 }
2961
2962 fn sample_bilinear_partial_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
2963 if !col_f.is_finite() || !row_f.is_finite() {
2964 return None;
2965 }
2966 let c0 = col_f.floor() as isize;
2967 let r0 = row_f.floor() as isize;
2968 let c1 = c0 + 1;
2969 let r1 = r0 + 1;
2970
2971 let tx = col_f - c0 as f64;
2972 let ty = row_f - r0 as f64;
2973
2974 let neighbors = [
2975 (c0, r0, (1.0 - tx) * (1.0 - ty)),
2976 (c1, r0, tx * (1.0 - ty)),
2977 (c0, r1, (1.0 - tx) * ty),
2978 (c1, r1, tx * ty),
2979 ];
2980
2981 let mut sum = 0.0;
2982 let mut wsum = 0.0;
2983 for (c, r, w) in neighbors {
2984 if w <= 0.0 || c < 0 || r < 0 || c >= self.cols as isize || r >= self.rows as isize {
2985 continue;
2986 }
2987 if let Some(v) = self.get_opt(band, r, c) {
2988 sum += v * w;
2989 wsum += w;
2990 }
2991 }
2992
2993 if wsum > 0.0 {
2994 Some(sum / wsum)
2995 } else {
2996 None
2997 }
2998 }
2999
3000 fn sample_cubic_strict_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
3001 if !col_f.is_finite() || !row_f.is_finite() {
3002 return None;
3003 }
3004 let c1 = col_f.floor() as isize;
3005 let r1 = row_f.floor() as isize;
3006 if c1 - 1 < 0 || r1 - 1 < 0 || c1 + 2 >= self.cols as isize || r1 + 2 >= self.rows as isize
3007 {
3008 return None;
3009 }
3010
3011 let tx = col_f - c1 as f64;
3012 let ty = row_f - r1 as f64;
3013 let wx = cubic_bspline_weights(tx);
3014 let wy = cubic_bspline_weights(ty);
3015
3016 self.sample_cubic_simd_kernel(&wx, &wy, band, c1 - 1, r1 - 1)
3018 }
3019
3020 #[inline]
3023 fn sample_cubic_simd_kernel(
3024 &self,
3025 wx: &[f64; 4],
3026 wy: &[f64; 4],
3027 band: isize,
3028 c_start: isize,
3029 r_start: isize,
3030 ) -> Option<f64> {
3031 let mut row_sums = [0.0_f64; 4];
3034
3035 for (j, _wyj) in wy.iter().enumerate() {
3036 let rr = r_start + j as isize;
3037 let mut row_sum = 0.0;
3038
3039 for (i, wxi) in wx.iter().enumerate() {
3041 let cc = c_start + i as isize;
3042 let v = self.get_opt(band, rr, cc)?;
3043 row_sum += v * *wxi;
3044 }
3045
3046 row_sums[j] = row_sum;
3047 }
3048
3049 let mut sum = 0.0;
3051 for (j, wyj) in wy.iter().enumerate() {
3052 sum += row_sums[j] * *wyj;
3053 }
3054
3055 Some(sum)
3056 }
3057
3058 fn sample_cubic_partial_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
3059 if !col_f.is_finite() || !row_f.is_finite() {
3060 return None;
3061 }
3062 let c1 = col_f.floor() as isize;
3063 let r1 = row_f.floor() as isize;
3064 let tx = col_f - c1 as f64;
3065 let ty = row_f - r1 as f64;
3066 let wx = cubic_bspline_weights(tx);
3067 let wy = cubic_bspline_weights(ty);
3068
3069 let mut sum = 0.0;
3070 let mut wsum = 0.0;
3071
3072 for (j, wyj) in wy.iter().enumerate() {
3073 let rr = clamp_isize(r1 + j as isize - 1, 0, self.rows as isize - 1);
3074 if *wyj <= 0.0 {
3075 continue;
3076 }
3077 for (i, wxi) in wx.iter().enumerate() {
3078 let cc = clamp_isize(c1 + i as isize - 1, 0, self.cols as isize - 1);
3079 let w = *wxi * *wyj;
3080 if w <= 0.0 {
3081 continue;
3082 }
3083 if let Some(v) = self.get_opt(band, rr, cc) {
3084 sum += v * w;
3085 wsum += w;
3086 }
3087 }
3088 }
3089
3090 if wsum > 0.0 {
3091 Some(sum / wsum)
3092 } else {
3093 None
3094 }
3095 }
3096
3097 fn sample_lanczos_strict_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
3098 if !col_f.is_finite() || !row_f.is_finite() {
3099 return None;
3100 }
3101
3102 let c0 = col_f.floor() as isize;
3103 let r0 = row_f.floor() as isize;
3104 if c0 - 2 < 0 || r0 - 2 < 0 || c0 + 3 >= self.cols as isize || r0 + 3 >= self.rows as isize
3105 {
3106 return None;
3107 }
3108
3109 let wx = lanczos3_weights(col_f, c0);
3110 let wy = lanczos3_weights(row_f, r0);
3111
3112 if let Some(values) = self.data_f64() {
3113 return self.sample_lanczos_strict_simd_f64(values, band, c0, r0, &wx, &wy);
3114 }
3115 if let Some(values) = self.data_f32() {
3116 return self.sample_lanczos_strict_simd_f32(values, band, c0, r0, &wx, &wy);
3117 }
3118
3119 let mut sum = 0.0;
3120 let mut wsum = 0.0;
3121 for (j, wyj) in wy.iter().enumerate() {
3122 let rr = r0 + j as isize - 2;
3123 for (i, wxi) in wx.iter().enumerate() {
3124 let cc = c0 + i as isize - 2;
3125 let v = self.get_opt(band, rr, cc)?;
3126 let w = *wxi * *wyj;
3127 sum += v * w;
3128 wsum += w;
3129 }
3130 }
3131
3132 if wsum.abs() > 1e-12 {
3133 Some(sum / wsum)
3134 } else {
3135 None
3136 }
3137 }
3138
3139 #[inline]
3140 fn sample_lanczos_strict_simd_f64(
3141 &self,
3142 values: &[f64],
3143 band: isize,
3144 c0: isize,
3145 r0: isize,
3146 wx: &[f64; 6],
3147 wy: &[f64; 6],
3148 ) -> Option<f64> {
3149 let band_stride = self.rows * self.cols;
3150 let base = band as usize * band_stride;
3151
3152 let wx0 = f64x4::new([wx[0], wx[1], wx[2], wx[3]]);
3153 let wx1 = f64x4::new([wx[4], wx[5], 0.0, 0.0]);
3154 let wx_sum: f64 = wx.iter().copied().sum();
3155
3156 let mut sum = 0.0;
3157 let mut wsum = 0.0;
3158 for (j, wyj) in wy.iter().enumerate() {
3159 let rr = (r0 + j as isize - 2) as usize;
3160 let cc = (c0 - 2) as usize;
3161 let row_offset = base + rr * self.cols + cc;
3162
3163 let v = [
3164 values[row_offset],
3165 values[row_offset + 1],
3166 values[row_offset + 2],
3167 values[row_offset + 3],
3168 values[row_offset + 4],
3169 values[row_offset + 5],
3170 ];
3171 if v.into_iter().any(|cell| self.is_nodata(cell)) {
3172 return None;
3173 }
3174
3175 let v0 = f64x4::new([v[0], v[1], v[2], v[3]]);
3176 let v1 = f64x4::new([v[4], v[5], 0.0, 0.0]);
3177 let d0 = <[f64; 4]>::from(v0 * wx0);
3178 let d1 = <[f64; 4]>::from(v1 * wx1);
3179 let row_sum = d0.into_iter().sum::<f64>() + d1.into_iter().sum::<f64>();
3180 sum += row_sum * *wyj;
3181 wsum += wx_sum * *wyj;
3182 }
3183
3184 if wsum.abs() > 1e-12 {
3185 Some(sum / wsum)
3186 } else {
3187 None
3188 }
3189 }
3190
3191 #[inline]
3192 fn sample_lanczos_strict_simd_f32(
3193 &self,
3194 values: &[f32],
3195 band: isize,
3196 c0: isize,
3197 r0: isize,
3198 wx: &[f64; 6],
3199 wy: &[f64; 6],
3200 ) -> Option<f64> {
3201 let band_stride = self.rows * self.cols;
3202 let base = band as usize * band_stride;
3203
3204 let wx0 = f64x4::new([wx[0], wx[1], wx[2], wx[3]]);
3205 let wx1 = f64x4::new([wx[4], wx[5], 0.0, 0.0]);
3206 let wx_sum: f64 = wx.iter().copied().sum();
3207
3208 let mut sum = 0.0;
3209 let mut wsum = 0.0;
3210 for (j, wyj) in wy.iter().enumerate() {
3211 let rr = (r0 + j as isize - 2) as usize;
3212 let cc = (c0 - 2) as usize;
3213 let row_offset = base + rr * self.cols + cc;
3214
3215 let v = [
3216 values[row_offset] as f64,
3217 values[row_offset + 1] as f64,
3218 values[row_offset + 2] as f64,
3219 values[row_offset + 3] as f64,
3220 values[row_offset + 4] as f64,
3221 values[row_offset + 5] as f64,
3222 ];
3223 if v.into_iter().any(|cell| self.is_nodata(cell)) {
3224 return None;
3225 }
3226
3227 let v0 = f64x4::new([v[0], v[1], v[2], v[3]]);
3228 let v1 = f64x4::new([v[4], v[5], 0.0, 0.0]);
3229 let d0 = <[f64; 4]>::from(v0 * wx0);
3230 let d1 = <[f64; 4]>::from(v1 * wx1);
3231 let row_sum = d0.into_iter().sum::<f64>() + d1.into_iter().sum::<f64>();
3232 sum += row_sum * *wyj;
3233 wsum += wx_sum * *wyj;
3234 }
3235
3236 if wsum.abs() > 1e-12 {
3237 Some(sum / wsum)
3238 } else {
3239 None
3240 }
3241 }
3242
3243 fn sample_lanczos_partial_pixel(&self, band: isize, col_f: f64, row_f: f64) -> Option<f64> {
3244 if !col_f.is_finite() || !row_f.is_finite() {
3245 return None;
3246 }
3247
3248 let c0 = col_f.floor() as isize;
3249 let r0 = row_f.floor() as isize;
3250 let wx = lanczos3_weights(col_f, c0);
3251 let wy = lanczos3_weights(row_f, r0);
3252
3253 let mut sum = 0.0;
3254 let mut wsum = 0.0;
3255
3256 for (j, wyj) in wy.iter().enumerate() {
3257 let rr = clamp_isize(r0 + j as isize - 2, 0, self.rows as isize - 1);
3258 for (i, wxi) in wx.iter().enumerate() {
3259 let cc = clamp_isize(c0 + i as isize - 2, 0, self.cols as isize - 1);
3260 let w = *wxi * *wyj;
3261 if w == 0.0 {
3262 continue;
3263 }
3264 if let Some(v) = self.get_opt(band, rr, cc) {
3265 sum += v * w;
3266 wsum += w;
3267 }
3268 }
3269 }
3270
3271 if wsum.abs() > 1e-12 {
3272 Some(sum / wsum)
3273 } else {
3274 None
3275 }
3276 }
3277
3278 fn stats_accumulator_range_with_mode(
3281 &self,
3282 start: usize,
3283 end: usize,
3284 mode: StatisticsComputationMode,
3285 ) -> StatsAccumulator {
3286 match mode {
3287 StatisticsComputationMode::Auto | StatisticsComputationMode::Simd => {
3288 self.stats_accumulator_range_simd(start, end)
3289 }
3290 StatisticsComputationMode::Scalar => self.stats_accumulator_range_scalar(start, end),
3291 }
3292 }
3293
3294 fn stats_accumulator_range_scalar(&self, start: usize, end: usize) -> StatsAccumulator {
3295 let mut accumulator = StatsAccumulator::default();
3296
3297 for idx in start..end {
3298 let value = self.data.get_f64(idx);
3299 if self.is_nodata(value) {
3300 accumulator.nodata_count += 1;
3301 } else {
3302 accumulator.min = accumulator.min.min(value);
3303 accumulator.max = accumulator.max.max(value);
3304 accumulator.sum += value;
3305 accumulator.sum_sq += value * value;
3306 accumulator.valid_count += 1;
3307 }
3308 }
3309
3310 accumulator
3311 }
3312
3313 fn stats_accumulator_range_simd(&self, start: usize, end: usize) -> StatsAccumulator {
3317 if let Some(values) = self.data_f64() {
3318 return self.stats_accumulator_range_simd_f64(values, start, end);
3319 }
3320 if let Some(values) = self.data_f32() {
3321 return self.stats_accumulator_range_simd_f32(values, start, end);
3322 }
3323
3324 self.stats_accumulator_range_scalar(start, end)
3325 }
3326
3327 fn stats_accumulator_range_simd_f64(
3328 &self,
3329 values: &[f64],
3330 start: usize,
3331 end: usize,
3332 ) -> StatsAccumulator {
3333 let mut accumulator = StatsAccumulator::default();
3334 let nd = self.nodata;
3335
3336 let simd_end = start + ((end - start) / 4) * 4;
3337 let mut simd_min = f64x4::splat(f64::INFINITY);
3338 let mut simd_max = f64x4::splat(f64::NEG_INFINITY);
3339 let mut simd_sum = f64x4::splat(0.0);
3340 let mut simd_sum_sq = f64x4::splat(0.0);
3341 let zero_v = f64x4::splat(0.0);
3342 let nd_v = f64x4::splat(nd);
3343 let inf_v = f64x4::splat(f64::INFINITY);
3344 let neg_inf_v = f64x4::splat(f64::NEG_INFINITY);
3345
3346 let mut idx = start;
3347 while idx < simd_end {
3348 let chunk = &values[idx..idx + 4];
3349 let values = f64x4::new([chunk[0], chunk[1], chunk[2], chunk[3]]);
3350
3351 let not_nodata = values.simd_ne(nd_v);
3352
3353 let values_for_min = not_nodata.blend(values, inf_v);
3354 let values_for_max = not_nodata.blend(values, neg_inf_v);
3355 simd_min = simd_min.min(values_for_min);
3356 simd_max = simd_max.max(values_for_max);
3357
3358 let masked_values = not_nodata.blend(values, zero_v);
3359 simd_sum = simd_sum + masked_values;
3360 simd_sum_sq = simd_sum_sq + masked_values * masked_values;
3361
3362 for &val in chunk {
3363 if val == nd {
3364 accumulator.nodata_count += 1;
3365 } else {
3366 accumulator.valid_count += 1;
3367 }
3368 }
3369
3370 idx += 4;
3371 }
3372
3373 let sum_arr = <[f64; 4]>::from(simd_sum);
3374 let sum_sq_arr = <[f64; 4]>::from(simd_sum_sq);
3375 let min_arr = <[f64; 4]>::from(simd_min);
3376 let max_arr = <[f64; 4]>::from(simd_max);
3377
3378 for i in 0..4 {
3379 accumulator.sum += sum_arr[i];
3380 accumulator.sum_sq += sum_sq_arr[i];
3381 accumulator.min = accumulator.min.min(min_arr[i]);
3382 accumulator.max = accumulator.max.max(max_arr[i]);
3383 }
3384
3385 for &value in &values[simd_end..end] {
3386 if value == nd {
3387 accumulator.nodata_count += 1;
3388 } else {
3389 accumulator.min = accumulator.min.min(value);
3390 accumulator.max = accumulator.max.max(value);
3391 accumulator.sum += value;
3392 accumulator.sum_sq += value * value;
3393 accumulator.valid_count += 1;
3394 }
3395 }
3396
3397 accumulator
3398 }
3399
3400 fn stats_accumulator_range_simd_f32(
3401 &self,
3402 values: &[f32],
3403 start: usize,
3404 end: usize,
3405 ) -> StatsAccumulator {
3406 let mut accumulator = StatsAccumulator::default();
3407 let nd = self.nodata as f32;
3408
3409 let simd_end = start + ((end - start) / 4) * 4;
3410 let mut simd_min = f64x4::splat(f64::INFINITY);
3411 let mut simd_max = f64x4::splat(f64::NEG_INFINITY);
3412 let mut simd_sum = f64x4::splat(0.0);
3413 let mut simd_sum_sq = f64x4::splat(0.0);
3414 let zero_v = f64x4::splat(0.0);
3415 let nd_v = f64x4::splat(nd as f64);
3416 let inf_v = f64x4::splat(f64::INFINITY);
3417 let neg_inf_v = f64x4::splat(f64::NEG_INFINITY);
3418
3419 let mut idx = start;
3420 while idx < simd_end {
3421 let chunk = &values[idx..idx + 4];
3422 let values = f64x4::new([
3423 chunk[0] as f64,
3424 chunk[1] as f64,
3425 chunk[2] as f64,
3426 chunk[3] as f64,
3427 ]);
3428
3429 let not_nodata = values.simd_ne(nd_v);
3430 let values_for_min = not_nodata.blend(values, inf_v);
3431 let values_for_max = not_nodata.blend(values, neg_inf_v);
3432 simd_min = simd_min.min(values_for_min);
3433 simd_max = simd_max.max(values_for_max);
3434
3435 let masked_values = not_nodata.blend(values, zero_v);
3436 simd_sum = simd_sum + masked_values;
3437 simd_sum_sq = simd_sum_sq + masked_values * masked_values;
3438
3439 for &val in chunk {
3440 if val == nd {
3441 accumulator.nodata_count += 1;
3442 } else {
3443 accumulator.valid_count += 1;
3444 }
3445 }
3446
3447 idx += 4;
3448 }
3449
3450 let sum_arr = <[f64; 4]>::from(simd_sum);
3451 let sum_sq_arr = <[f64; 4]>::from(simd_sum_sq);
3452 let min_arr = <[f64; 4]>::from(simd_min);
3453 let max_arr = <[f64; 4]>::from(simd_max);
3454
3455 for i in 0..4 {
3456 accumulator.sum += sum_arr[i];
3457 accumulator.sum_sq += sum_sq_arr[i];
3458 accumulator.min = accumulator.min.min(min_arr[i]);
3459 accumulator.max = accumulator.max.max(max_arr[i]);
3460 }
3461
3462 for &value in &values[simd_end..end] {
3463 if value == nd {
3464 accumulator.nodata_count += 1;
3465 } else {
3466 let value = value as f64;
3467 accumulator.min = accumulator.min.min(value);
3468 accumulator.max = accumulator.max.max(value);
3469 accumulator.sum += value;
3470 accumulator.sum_sq += value * value;
3471 accumulator.valid_count += 1;
3472 }
3473 }
3474
3475 accumulator
3476 }
3477
3478 fn statistics_for_index_range_with_mode(
3479 &self,
3480 start: usize,
3481 end: usize,
3482 mode: StatisticsComputationMode,
3483 ) -> Statistics {
3484 const PARALLEL_THRESHOLD: usize = 65_536;
3485 const CHUNK_SIZE: usize = 16_384;
3486
3487 let span = end.saturating_sub(start);
3488 if span == 0 {
3489 return Statistics {
3490 min: 0.0,
3491 max: 0.0,
3492 mean: 0.0,
3493 std_dev: 0.0,
3494 valid_count: 0,
3495 nodata_count: 0,
3496 };
3497 }
3498
3499 let total = if span < PARALLEL_THRESHOLD {
3500 self.stats_accumulator_range_with_mode(start, end, mode)
3501 } else {
3502 let chunk_starts: Vec<usize> = (start..end).step_by(CHUNK_SIZE).collect();
3503 let partials: Vec<StatsAccumulator> = chunk_starts
3504 .into_par_iter()
3505 .map(|chunk_start| {
3506 let chunk_end = (chunk_start + CHUNK_SIZE).min(end);
3507 self.stats_accumulator_range_with_mode(chunk_start, chunk_end, mode)
3508 })
3509 .collect();
3510
3511 partials
3512 .into_iter()
3513 .fold(StatsAccumulator::default(), |mut lhs, rhs| {
3514 lhs.merge(rhs);
3515 lhs
3516 })
3517 };
3518
3519 total.to_statistics()
3520 }
3521
3522 pub fn statistics(&self) -> Statistics {
3524 self.statistics_with_mode(StatisticsComputationMode::Auto)
3525 }
3526
3527 pub fn statistics_with_mode(&self, mode: StatisticsComputationMode) -> Statistics {
3529 self.statistics_for_index_range_with_mode(0, self.data.len(), mode)
3530 }
3531
3532 pub fn statistics_band(&self, band: isize) -> Result<Statistics> {
3534 self.statistics_band_with_mode(band, StatisticsComputationMode::Auto)
3535 }
3536
3537 pub fn statistics_band_with_mode(
3539 &self,
3540 band: isize,
3541 mode: StatisticsComputationMode,
3542 ) -> Result<Statistics> {
3543 if band < 0 || band >= self.bands as isize {
3544 return Err(RasterError::OutOfBounds {
3545 band,
3546 col: 0,
3547 row: 0,
3548 bands: self.bands,
3549 cols: self.cols,
3550 rows: self.rows,
3551 });
3552 }
3553
3554 let band = band as usize;
3555 let band_stride = self.rows * self.cols;
3556 let start = band * band_stride;
3557 let end = start + band_stride;
3558
3559 Ok(self.statistics_for_index_range_with_mode(start, end, mode))
3560 }
3561
3562 #[inline]
3566 pub fn band_slice(&self, band: isize) -> Vec<f64> {
3567 if band < 0 || band >= self.bands as isize {
3568 return Vec::new();
3569 }
3570 let band_stride = self.rows * self.cols;
3571 let start = band as usize * band_stride;
3572 (start..start + band_stride)
3573 .map(|i| self.data.get_f64(i))
3574 .collect()
3575 }
3576
3577 pub fn set_band_slice(&mut self, band: isize, values: &[f64]) -> Result<()> {
3579 let expected = self.rows * self.cols;
3580 if values.len() != expected {
3581 return Err(RasterError::InvalidDimensions {
3582 cols: self.cols,
3583 rows: values.len(),
3584 });
3585 }
3586 if band < 0 || band >= self.bands as isize {
3587 return Err(RasterError::OutOfBounds {
3588 band,
3589 col: 0,
3590 row: 0,
3591 bands: self.bands,
3592 cols: self.cols,
3593 rows: self.rows,
3594 });
3595 }
3596 let band_stride = self.rows * self.cols;
3597 let start = band as usize * band_stride;
3598 for (i, v) in values.iter().copied().enumerate() {
3599 self.data.set_f64(start + i, v);
3600 }
3601 Ok(())
3602 }
3603
3604 #[inline]
3606 pub fn row_slice(&self, band: isize, row: isize) -> Vec<f64> {
3607 if band < 0 || band >= self.bands as isize || row < 0 || row >= self.rows as isize {
3608 return Vec::new();
3609 }
3610 let band_stride = self.rows * self.cols;
3611 let start = band as usize * band_stride + row as usize * self.cols;
3612 (start..start + self.cols)
3613 .map(|i| self.data.get_f64(i))
3614 .collect()
3615 }
3616
3617 pub fn set_row_slice(&mut self, band: isize, row: isize, values: &[f64]) -> Result<()> {
3619 if values.len() != self.cols {
3620 return Err(RasterError::InvalidDimensions {
3621 cols: values.len(),
3622 rows: self.rows,
3623 });
3624 }
3625 if band < 0 || band >= self.bands as isize || row < 0 || row >= self.rows as isize {
3626 return Err(RasterError::OutOfBounds {
3627 band,
3628 col: 0,
3629 row,
3630 bands: self.bands,
3631 cols: self.cols,
3632 rows: self.rows,
3633 });
3634 }
3635 let band_stride = self.rows * self.cols;
3636 let start = band as usize * band_stride + row as usize * self.cols;
3637 for (i, v) in values.iter().copied().enumerate() {
3638 self.data.set_f64(start + i, v);
3639 }
3640 Ok(())
3641 }
3642
3643 pub fn iter_valid(&self) -> impl Iterator<Item = (isize, isize, isize, f64)> + '_ {
3645 self.data
3646 .iter_f64()
3647 .enumerate()
3648 .filter_map(move |(idx, v)| {
3649 if self.is_nodata(v) {
3650 None
3651 } else {
3652 let band_stride = self.rows * self.cols;
3653 let band = idx / band_stride;
3654 let rem = idx % band_stride;
3655 let row = rem / self.cols;
3656 let col = rem % self.cols;
3657 Some((band as isize, row as isize, col as isize, v))
3658 }
3659 })
3660 }
3661
3662 pub fn iter_valid_band(
3664 &self,
3665 band: isize,
3666 ) -> Result<Box<dyn Iterator<Item = (isize, isize, f64)> + '_>> {
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 b = band as usize;
3679 let band_stride = self.rows * self.cols;
3680 let start = b * band_stride;
3681 Ok(Box::new((0..band_stride).filter_map(move |i| {
3682 let v = self.data.get_f64(start + i);
3683 if self.is_nodata(v) {
3684 None
3685 } else {
3686 let row = i / self.cols;
3687 let col = i % self.cols;
3688 Some((row as isize, col as isize, v))
3689 }
3690 })))
3691 }
3692
3693 pub fn iter_band_rows(&self, band: isize) -> Result<Box<dyn Iterator<Item = Vec<f64>> + '_>> {
3695 if band < 0 || band >= self.bands as isize {
3696 return Err(RasterError::OutOfBounds {
3697 band,
3698 col: 0,
3699 row: 0,
3700 bands: self.bands,
3701 cols: self.cols,
3702 rows: self.rows,
3703 });
3704 }
3705
3706 Ok(Box::new(
3707 (0..self.rows).map(move |row| self.row_slice(band, row as isize)),
3708 ))
3709 }
3710
3711 pub fn for_each_band_row_mut<F>(&mut self, band: isize, mut f: F) -> Result<()>
3716 where
3717 F: FnMut(isize, RasterRowMut<'_>),
3718 {
3719 if band < 0 || band >= self.bands as isize {
3720 return Err(RasterError::OutOfBounds {
3721 band,
3722 col: 0,
3723 row: 0,
3724 bands: self.bands,
3725 cols: self.cols,
3726 rows: self.rows,
3727 });
3728 }
3729
3730 let b = band as usize;
3731 let band_stride = self.rows * self.cols;
3732 let start = b * band_stride;
3733 let end = start + band_stride;
3734
3735 match &mut self.data {
3736 RasterData::U8(v) => {
3737 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3738 f(row as isize, RasterRowMut::U8(chunk));
3739 }
3740 }
3741 RasterData::I8(v) => {
3742 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3743 f(row as isize, RasterRowMut::I8(chunk));
3744 }
3745 }
3746 RasterData::U16(v) => {
3747 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3748 f(row as isize, RasterRowMut::U16(chunk));
3749 }
3750 }
3751 RasterData::I16(v) => {
3752 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3753 f(row as isize, RasterRowMut::I16(chunk));
3754 }
3755 }
3756 RasterData::U32(v) => {
3757 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3758 f(row as isize, RasterRowMut::U32(chunk));
3759 }
3760 }
3761 RasterData::I32(v) => {
3762 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3763 f(row as isize, RasterRowMut::I32(chunk));
3764 }
3765 }
3766 RasterData::U64(v) => {
3767 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3768 f(row as isize, RasterRowMut::U64(chunk));
3769 }
3770 }
3771 RasterData::I64(v) => {
3772 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3773 f(row as isize, RasterRowMut::I64(chunk));
3774 }
3775 }
3776 RasterData::F32(v) => {
3777 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3778 f(row as isize, RasterRowMut::F32(chunk));
3779 }
3780 }
3781 RasterData::F64(v) => {
3782 for (row, chunk) in v[start..end].chunks_mut(self.cols).enumerate() {
3783 f(row as isize, RasterRowMut::F64(chunk));
3784 }
3785 }
3786 }
3787
3788 Ok(())
3789 }
3790
3791 pub fn for_each_band_row<F>(&self, band: isize, mut f: F) -> Result<()>
3796 where
3797 F: FnMut(isize, RasterRowRef<'_>),
3798 {
3799 if band < 0 || band >= self.bands as isize {
3800 return Err(RasterError::OutOfBounds {
3801 band,
3802 col: 0,
3803 row: 0,
3804 bands: self.bands,
3805 cols: self.cols,
3806 rows: self.rows,
3807 });
3808 }
3809
3810 let b = band as usize;
3811 let band_stride = self.rows * self.cols;
3812 let start = b * band_stride;
3813 let end = start + band_stride;
3814
3815 match &self.data {
3816 RasterData::U8(v) => {
3817 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3818 f(row as isize, RasterRowRef::U8(chunk));
3819 }
3820 }
3821 RasterData::I8(v) => {
3822 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3823 f(row as isize, RasterRowRef::I8(chunk));
3824 }
3825 }
3826 RasterData::U16(v) => {
3827 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3828 f(row as isize, RasterRowRef::U16(chunk));
3829 }
3830 }
3831 RasterData::I16(v) => {
3832 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3833 f(row as isize, RasterRowRef::I16(chunk));
3834 }
3835 }
3836 RasterData::U32(v) => {
3837 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3838 f(row as isize, RasterRowRef::U32(chunk));
3839 }
3840 }
3841 RasterData::I32(v) => {
3842 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3843 f(row as isize, RasterRowRef::I32(chunk));
3844 }
3845 }
3846 RasterData::U64(v) => {
3847 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3848 f(row as isize, RasterRowRef::U64(chunk));
3849 }
3850 }
3851 RasterData::I64(v) => {
3852 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3853 f(row as isize, RasterRowRef::I64(chunk));
3854 }
3855 }
3856 RasterData::F32(v) => {
3857 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3858 f(row as isize, RasterRowRef::F32(chunk));
3859 }
3860 }
3861 RasterData::F64(v) => {
3862 for (row, chunk) in v[start..end].chunks(self.cols).enumerate() {
3863 f(row as isize, RasterRowRef::F64(chunk));
3864 }
3865 }
3866 }
3867
3868 Ok(())
3869 }
3870
3871 pub fn fill(&mut self, value: f64) {
3875 for i in 0..self.data.len() {
3876 self.data.set_f64(i, value);
3877 }
3878 }
3879
3880 pub fn fill_nodata(&mut self) {
3882 let nd = self.nodata;
3883 self.fill(nd);
3884 }
3885
3886 pub fn map_valid<F: Fn(f64) -> f64>(&mut self, f: F) {
3888 let nd = self.nodata;
3889 let nodata_nan = nd.is_nan();
3890 for i in 0..self.data.len() {
3891 let v = self.data.get_f64(i);
3892 let is_nd = if nodata_nan {
3893 v.is_nan()
3894 } else {
3895 (v - nd).abs() < 1e-10 * nd.abs().max(1.0)
3896 };
3897 if !is_nd {
3898 self.data.set_f64(i, f(v));
3899 }
3900 }
3901 }
3902
3903 pub fn map_valid_band<F: Fn(f64) -> f64>(&mut self, band: isize, f: F) -> Result<()> {
3905 if band < 0 || band >= self.bands as isize {
3906 return Err(RasterError::OutOfBounds {
3907 band,
3908 col: 0,
3909 row: 0,
3910 bands: self.bands,
3911 cols: self.cols,
3912 rows: self.rows,
3913 });
3914 }
3915
3916 let nd = self.nodata;
3917 let nodata_nan = nd.is_nan();
3918 let band_stride = self.rows * self.cols;
3919 let start = band as usize * band_stride;
3920 let end = start + band_stride;
3921
3922 for i in start..end {
3923 let v = self.data.get_f64(i);
3924 let is_nd = if nodata_nan {
3925 v.is_nan()
3926 } else {
3927 (v - nd).abs() < 1e-10 * nd.abs().max(1.0)
3928 };
3929 if !is_nd {
3930 self.data.set_f64(i, f(v));
3931 }
3932 }
3933 Ok(())
3934 }
3935
3936 pub fn replace(&mut self, from: f64, to: f64) {
3938 for i in 0..self.data.len() {
3939 let v = self.data.get_f64(i);
3940 if (v - from).abs() < f64::EPSILON {
3941 self.data.set_f64(i, to);
3942 }
3943 }
3944 }
3945
3946 pub fn replace_band(&mut self, band: isize, from: f64, to: f64) -> Result<()> {
3948 if band < 0 || band >= self.bands as isize {
3949 return Err(RasterError::OutOfBounds {
3950 band,
3951 col: 0,
3952 row: 0,
3953 bands: self.bands,
3954 cols: self.cols,
3955 rows: self.rows,
3956 });
3957 }
3958
3959 let band_stride = self.rows * self.cols;
3960 let start = band as usize * band_stride;
3961 let end = start + band_stride;
3962 for i in start..end {
3963 let v = self.data.get_f64(i);
3964 if (v - from).abs() < f64::EPSILON {
3965 self.data.set_f64(i, to);
3966 }
3967 }
3968 Ok(())
3969 }
3970
3971 pub fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
3975 let path = path.as_ref().to_string_lossy().to_string();
3976 if crate::formats::is_hdf_dataset_uri(&path) {
3977 return crate::formats::read_hdf_dataset_uri(&path);
3978 }
3979 let fmt = RasterFormat::detect(&path)?;
3980 fmt.read(&path)
3981 }
3982
3983 pub fn read_with_format<P: AsRef<Path>>(path: P, fmt: RasterFormat) -> Result<Self> {
3985 let path = path.as_ref().to_string_lossy().to_string();
3986 fmt.read(&path)
3987 }
3988
3989 pub fn write<P: AsRef<Path>>(&self, path: P, fmt: RasterFormat) -> Result<()> {
3991 let path = path.as_ref().to_string_lossy().to_string();
3992 fmt.write(self, &path)
3993 }
3994
3995 pub fn write_auto<P: AsRef<Path>>(&self, path: P) -> Result<()> {
3997 let path = path.as_ref().to_string_lossy().to_string();
3998 let fmt = RasterFormat::detect(&path)?;
3999 fmt.write(self, &path)
4000 }
4001
4002 pub fn write_geotiff_with_options<P: AsRef<Path>>(
4004 &self,
4005 path: P,
4006 opts: &crate::formats::geotiff::GeoTiffWriteOptions,
4007 ) -> Result<()> {
4008 let path = path.as_ref().to_string_lossy().to_string();
4009 crate::formats::geotiff::write_with_options(self, &path, opts)
4010 }
4011
4012 pub fn write_cog<P: AsRef<Path>>(&self, path: P) -> Result<()> {
4020 let opts = crate::formats::geotiff::GeoTiffWriteOptions {
4021 compression: Some(crate::formats::geotiff::GeoTiffCompression::Deflate),
4022 bigtiff: Some(false),
4023 layout: Some(crate::formats::geotiff::GeoTiffLayout::Cog { tile_size: 512 }),
4024 };
4025 self.write_geotiff_with_options(path, &opts)
4026 }
4027
4028 pub fn write_cog_with_tile_size<P: AsRef<Path>>(&self, path: P, tile_size: u32) -> Result<()> {
4036 let opts = crate::formats::geotiff::GeoTiffWriteOptions {
4037 compression: Some(crate::formats::geotiff::GeoTiffCompression::Deflate),
4038 bigtiff: Some(false),
4039 layout: Some(crate::formats::geotiff::GeoTiffLayout::Cog { tile_size }),
4040 };
4041 self.write_geotiff_with_options(path, &opts)
4042 }
4043
4044 pub fn write_cog_with_options<P: AsRef<Path>>(
4052 &self,
4053 path: P,
4054 opts: &crate::formats::geotiff::CogWriteOptions,
4055 ) -> Result<()> {
4056 let geotiff_opts = crate::formats::geotiff::GeoTiffWriteOptions {
4057 compression: Some(
4058 opts.compression
4059 .unwrap_or(crate::formats::geotiff::GeoTiffCompression::Deflate),
4060 ),
4061 bigtiff: Some(opts.bigtiff.unwrap_or(false)),
4062 layout: Some(crate::formats::geotiff::GeoTiffLayout::Cog {
4063 tile_size: opts.tile_size.unwrap_or(512),
4064 }),
4065 };
4066 self.write_geotiff_with_options(path, &geotiff_opts)
4067 }
4068
4069 pub fn write_jpeg2000_with_options<P: AsRef<Path>>(
4071 &self,
4072 path: P,
4073 opts: &crate::formats::jpeg2000::Jpeg2000WriteOptions,
4074 ) -> Result<()> {
4075 let path = path.as_ref().to_string_lossy().to_string();
4076 crate::formats::jpeg2000::write_with_options(self, &path, opts)
4077 }
4078}
4079
4080fn maybe_warn_area_of_use_mismatch(src: &Crs, dst: &Crs, src_extent: Extent, enabled: bool) {
4081 if !enabled {
4082 return;
4083 }
4084
4085 let src_area = src.area_of_use();
4086 let dst_area = dst.area_of_use();
4087 if src_area.is_none() && dst_area.is_none() {
4088 return;
4089 }
4090
4091 let Ok(wgs84) = Crs::from_epsg(4326) else {
4092 return;
4093 };
4094
4095 let sample_points = [
4096 (src_extent.x_min, src_extent.y_min),
4097 (src_extent.x_min, src_extent.y_max),
4098 (src_extent.x_max, src_extent.y_min),
4099 (src_extent.x_max, src_extent.y_max),
4100 (
4101 0.5 * (src_extent.x_min + src_extent.x_max),
4102 0.5 * (src_extent.y_min + src_extent.y_max),
4103 ),
4104 ];
4105
4106 let mut src_outside = 0usize;
4107 let mut dst_outside = 0usize;
4108 let mut checked = 0usize;
4109
4110 for (x, y) in sample_points {
4111 let Ok((lon, lat)) = src.transform_to(x, y, &wgs84) else {
4112 continue;
4113 };
4114 checked += 1;
4115
4116 if let Some(bb) = &src_area {
4117 if !bb.contains_geographic(lon, lat) {
4118 src_outside += 1;
4119 }
4120 }
4121 if let Some(bb) = &dst_area {
4122 if !bb.contains_geographic(lon, lat) {
4123 dst_outside += 1;
4124 }
4125 }
4126 }
4127
4128 if checked == 0 {
4129 return;
4130 }
4131
4132 if src_outside > 0 || dst_outside > 0 {
4133 eprintln!(
4134 "wbraster reprojection warning: sampled source extent appears outside CRS area of use (src outside: {src_outside}/{checked}, dst outside: {dst_outside}/{checked})"
4135 );
4136 }
4137}
4138
4139fn cubic_bspline_weights(t: f64) -> [f64; 4] {
4140 let t = t.clamp(0.0, 1.0);
4141 let t2 = t * t;
4142 let t3 = t2 * t;
4143 [
4144 ((1.0 - t) * (1.0 - t) * (1.0 - t)) / 6.0,
4145 (3.0 * t3 - 6.0 * t2 + 4.0) / 6.0,
4146 (-3.0 * t3 + 3.0 * t2 + 3.0 * t + 1.0) / 6.0,
4147 t3 / 6.0,
4148 ]
4149}
4150
4151fn lanczos_kernel(x: f64, a: f64) -> f64 {
4152 if x.abs() < 1e-12 {
4153 return 1.0;
4154 }
4155 if x.abs() >= a {
4156 return 0.0;
4157 }
4158 let pix = std::f64::consts::PI * x;
4159 let pix_over_a = pix / a;
4160 (pix.sin() / pix) * (pix_over_a.sin() / pix_over_a)
4161}
4162
4163fn lanczos3_weights(sample_f: f64, floor_idx: isize) -> [f64; 6] {
4164 let mut w = [0.0_f64; 6];
4165 for (i, wi) in w.iter_mut().enumerate() {
4166 let idx = floor_idx + i as isize - 2;
4167 let dx = sample_f - idx as f64;
4168 *wi = lanczos_kernel(dx, 3.0);
4169 }
4170 w
4171}
4172
4173fn sample_extent_boundary_points(extent: Extent, samples_per_edge: usize) -> Vec<(f64, f64)> {
4174 let n = samples_per_edge.max(1);
4175 let mut pts = Vec::with_capacity(4 * n);
4176
4177 for i in 0..=n {
4179 let t = i as f64 / n as f64;
4180 let x = extent.x_min + t * (extent.x_max - extent.x_min);
4181 pts.push((x, extent.y_min));
4182 pts.push((x, extent.y_max));
4183 }
4184
4185 for j in 1..n {
4187 let t = j as f64 / n as f64;
4188 let y = extent.y_min + t * (extent.y_max - extent.y_min);
4189 pts.push((extent.x_min, y));
4190 pts.push((extent.x_max, y));
4191 }
4192
4193 pts
4194}
4195
4196fn sample_extent_boundary_ring(extent: Extent, samples_per_edge: usize) -> Vec<(f64, f64)> {
4197 let n = samples_per_edge.max(1);
4198 let mut ring = Vec::with_capacity(n * 4);
4199
4200 for i in 0..n {
4201 let t = if n == 1 {
4202 0.0
4203 } else {
4204 i as f64 / (n - 1) as f64
4205 };
4206 let x = extent.x_min + t * (extent.x_max - extent.x_min);
4207 ring.push((x, extent.y_min));
4208 }
4209
4210 for i in 0..n {
4211 let t = if n == 1 {
4212 0.0
4213 } else {
4214 i as f64 / (n - 1) as f64
4215 };
4216 let y = extent.y_min + t * (extent.y_max - extent.y_min);
4217 ring.push((extent.x_max, y));
4218 }
4219
4220 for i in 0..n {
4221 let t = if n == 1 {
4222 0.0
4223 } else {
4224 i as f64 / (n - 1) as f64
4225 };
4226 let x = extent.x_max - t * (extent.x_max - extent.x_min);
4227 ring.push((x, extent.y_max));
4228 }
4229
4230 for i in 0..n {
4231 let t = if n == 1 {
4232 0.0
4233 } else {
4234 i as f64 / (n - 1) as f64
4235 };
4236 let y = extent.y_max - t * (extent.y_max - extent.y_min);
4237 ring.push((extent.x_min, y));
4238 }
4239
4240 ring
4241}
4242
4243fn transformed_extent_from_boundary_samples(
4244 src_crs: &Crs,
4245 dst_crs: &Crs,
4246 src_extent: Extent,
4247 samples_per_edge: usize,
4248 dst_epsg: u32,
4249 antimeridian_policy: AntimeridianPolicy,
4250 epoch_transform: &EpochTransformOptions,
4251) -> Result<Extent> {
4252 let points = sample_extent_boundary_points(src_extent, samples_per_edge);
4253
4254 let mut tx_min = f64::INFINITY;
4255 let mut tx_max = f64::NEG_INFINITY;
4256 let mut ty_min = f64::INFINITY;
4257 let mut ty_max = f64::NEG_INFINITY;
4258 let mut tx_values = Vec::new();
4259 let mut valid = 0usize;
4260
4261 for (x, y) in points {
4262 let Ok((tx, ty)) = transform_xy_with_epoch_options(src_crs, dst_crs, x, y, epoch_transform)
4263 else {
4264 continue;
4265 };
4266 if !tx.is_finite() || !ty.is_finite() {
4267 continue;
4268 }
4269 tx_min = tx_min.min(tx);
4270 tx_max = tx_max.max(tx);
4271 ty_min = ty_min.min(ty);
4272 ty_max = ty_max.max(ty);
4273 tx_values.push(tx);
4274 valid += 1;
4275 }
4276
4277 if valid == 0 {
4278 return Err(RasterError::Other(format!(
4279 "failed to transform source extent boundary samples to EPSG:{}",
4280 dst_epsg
4281 )));
4282 }
4283
4284 if dst_epsg == 4326 {
4285 if let Some((x0, x1)) = antimeridian_aware_longitude_bounds(&tx_values, antimeridian_policy)
4286 {
4287 tx_min = x0;
4288 tx_max = x1;
4289 }
4290 }
4291
4292 Ok(Extent {
4293 x_min: tx_min,
4294 y_min: ty_min,
4295 x_max: tx_max,
4296 y_max: ty_max,
4297 })
4298}
4299
4300fn transformed_boundary_ring_samples(
4301 src_crs: &Crs,
4302 dst_crs: &Crs,
4303 src_extent: Extent,
4304 samples_per_edge: usize,
4305 dst_epsg: u32,
4306 antimeridian_policy: AntimeridianPolicy,
4307 epoch_transform: &EpochTransformOptions,
4308) -> Result<Vec<(f64, f64)>> {
4309 let ring = sample_extent_boundary_ring(src_extent, samples_per_edge);
4310 let mut transformed = Vec::with_capacity(ring.len());
4311
4312 for (x, y) in ring {
4313 let Ok((tx, ty)) = transform_xy_with_epoch_options(src_crs, dst_crs, x, y, epoch_transform)
4314 else {
4315 continue;
4316 };
4317 if tx.is_finite() && ty.is_finite() {
4318 transformed.push((tx, ty));
4319 }
4320 }
4321
4322 if transformed.len() < 3 {
4323 return Err(RasterError::Other(format!(
4324 "failed to build transformed boundary ring for EPSG:{}",
4325 dst_epsg
4326 )));
4327 }
4328
4329 if dst_epsg == 4326 && antimeridian_policy != AntimeridianPolicy::Linear {
4330 let lons: Vec<f64> = transformed.iter().map(|(x, _)| *x).collect();
4331 if let Some((base, _)) = minimal_wrapped_longitude_bounds(&lons) {
4332 for (x, _) in &mut transformed {
4333 let mut w = wrap_lon_360(*x);
4334 if w < base {
4335 w += 360.0;
4336 }
4337 *x = w;
4338 }
4339 }
4340 }
4341
4342 Ok(transformed)
4343}
4344
4345fn snap_down_to_origin(value: f64, origin: f64, step: f64) -> f64 {
4346 origin + ((value - origin) / step).floor() * step
4347}
4348
4349fn snap_up_to_origin(value: f64, origin: f64, step: f64) -> f64 {
4350 origin + ((value - origin) / step).ceil() * step
4351}
4352
4353fn transform_xy_with_epoch_options(
4354 src: &Crs,
4355 dst: &Crs,
4356 x: f64,
4357 y: f64,
4358 options: &EpochTransformOptions,
4359) -> Result<(f64, f64)> {
4360 options
4361 .validate()
4362 .map_err(|e| RasterError::Other(format!("invalid epoch transform options: {e}")))?;
4363 let ctx = options
4364 .build_context()
4365 .map_err(|e| RasterError::Other(format!("invalid epoch transform options: {e}")))?;
4366 let epoch_routing_requested = options.coordinate_epoch_decimal_year.is_some()
4367 || options.source_reference_epoch_decimal_year.is_some()
4368 || options.target_reference_epoch_decimal_year.is_some()
4369 || options.operation_code.is_some()
4370 || !options.prefer_official_operation
4371 || matches!(options.epoch_policy, EpochPolicy::AllowStaticFallback);
4372
4373 if !epoch_routing_requested {
4374 return src
4375 .transform_to(x, y, dst)
4376 .map_err(|err| RasterError::Other(format!("epoch-aware transform failed: {err}")));
4377 }
4378
4379 let result = if let Some(operation_code) = options.operation_code {
4380 src.transform_to_with_operation(x, y, dst, operation_code, ctx)
4381 } else if options.prefer_official_operation {
4382 src.transform_to_with_preferred_operation(x, y, dst, ctx)
4383 } else if let Some(epoch_ctx) = ctx {
4384 src.transform_to_with_context(x, y, dst, epoch_ctx)
4385 } else {
4386 src.transform_to_with_policy(x, y, dst, CrsTransformPolicy::Auto)
4387 };
4388
4389 match result {
4390 Ok(v) => Ok(v),
4391 Err(err) if matches!(options.epoch_policy, EpochPolicy::AllowStaticFallback) => src
4392 .transform_to_with_policy(x, y, dst, CrsTransformPolicy::Auto)
4393 .map_err(|fallback_err| {
4394 RasterError::Other(format!(
4395 "epoch-aware transform failed ({err}); static fallback failed ({fallback_err})"
4396 ))
4397 }),
4398 Err(err) => Err(RasterError::Other(format!(
4399 "epoch-aware transform failed: {err}"
4400 ))),
4401 }
4402}
4403
4404fn wrap_lon_360(lon: f64) -> f64 {
4405 let mut v = lon % 360.0;
4406 if v < 0.0 {
4407 v += 360.0;
4408 }
4409 v
4410}
4411
4412fn minimal_wrapped_longitude_bounds(longitudes: &[f64]) -> Option<(f64, f64)> {
4413 if longitudes.is_empty() {
4414 return None;
4415 }
4416
4417 if longitudes.len() == 1 {
4418 let v = wrap_lon_360(longitudes[0]);
4419 return Some((v, v));
4420 }
4421
4422 let mut values: Vec<f64> = longitudes.iter().map(|v| wrap_lon_360(*v)).collect();
4423 values.sort_by(f64::total_cmp);
4424
4425 let n = values.len();
4426 let mut max_gap = f64::NEG_INFINITY;
4427 let mut max_gap_idx = 0usize;
4428 for i in 0..n {
4429 let next = if i + 1 < n {
4430 values[i + 1]
4431 } else {
4432 values[0] + 360.0
4433 };
4434 let gap = next - values[i];
4435 if gap > max_gap {
4436 max_gap = gap;
4437 max_gap_idx = i;
4438 }
4439 }
4440
4441 let start = values[(max_gap_idx + 1) % n];
4442 let mut end = values[max_gap_idx];
4443 if end < start {
4444 end += 360.0;
4445 }
4446
4447 Some((start, end))
4448}
4449
4450fn antimeridian_aware_longitude_bounds(
4451 longitudes: &[f64],
4452 policy: AntimeridianPolicy,
4453) -> Option<(f64, f64)> {
4454 if longitudes.is_empty() {
4455 return None;
4456 }
4457
4458 let mut lin_min = f64::INFINITY;
4459 let mut lin_max = f64::NEG_INFINITY;
4460 for lon in longitudes {
4461 if !lon.is_finite() {
4462 continue;
4463 }
4464 lin_min = lin_min.min(*lon);
4465 lin_max = lin_max.max(*lon);
4466 }
4467 if !lin_min.is_finite() || !lin_max.is_finite() {
4468 return None;
4469 }
4470
4471 if policy == AntimeridianPolicy::Linear {
4472 return Some((lin_min, lin_max));
4473 }
4474
4475 let Some((wrap_min, wrap_max)) = minimal_wrapped_longitude_bounds(longitudes) else {
4476 return Some((lin_min, lin_max));
4477 };
4478 if policy == AntimeridianPolicy::Wrap {
4479 return Some((wrap_min, wrap_max));
4480 }
4481
4482 let linear_width = lin_max - lin_min;
4483 let wrapped_width = wrap_max - wrap_min;
4484
4485 if wrapped_width + 1e-9 < linear_width {
4486 Some((wrap_min, wrap_max))
4487 } else {
4488 Some((lin_min, lin_max))
4489 }
4490}
4491
4492fn clamp_isize(v: isize, min_v: isize, max_v: isize) -> isize {
4493 v.max(min_v).min(max_v)
4494}
4495
4496fn point_in_polygon(x: f64, y: f64, polygon: &[(f64, f64)]) -> bool {
4497 if polygon.len() < 3 {
4498 return false;
4499 }
4500 let mut inside = false;
4501 let mut j = polygon.len() - 1;
4502 for i in 0..polygon.len() {
4503 let (xi, yi) = polygon[i];
4504 let (xj, yj) = polygon[j];
4505 let intersects =
4506 (yi > y) != (yj > y) && x < (xj - xi) * (y - yi) / ((yj - yi) + 1e-30) + xi;
4507 if intersects {
4508 inside = !inside;
4509 }
4510 j = i;
4511 }
4512 inside
4513}
4514
4515#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4516enum WindowStat {
4517 Mean,
4518 Min,
4519 Max,
4520 Mode,
4521 Median,
4522 StdDev,
4523}
4524
4525fn reduce_window_values(values: &[f64], stat: WindowStat) -> Option<f64> {
4526 if values.is_empty() {
4527 return None;
4528 }
4529
4530 match stat {
4531 WindowStat::Mean => Some(values.iter().sum::<f64>() / values.len() as f64),
4532 WindowStat::Min => values.iter().copied().reduce(f64::min),
4533 WindowStat::Max => values.iter().copied().reduce(f64::max),
4534 WindowStat::Mode => {
4535 let mut pairs: Vec<(f64, usize)> = Vec::new();
4536 for v in values {
4537 if let Some((_, count)) = pairs.iter_mut().find(|(u, _)| *u == *v) {
4538 *count += 1;
4539 } else {
4540 pairs.push((*v, 1));
4541 }
4542 }
4543 pairs.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.total_cmp(&b.0)));
4544 pairs.first().map(|(v, _)| *v)
4545 }
4546 WindowStat::Median => {
4547 let mut sorted = values.to_vec();
4548 sorted.sort_by(f64::total_cmp);
4549 let n = sorted.len();
4550 if n % 2 == 1 {
4551 Some(sorted[n / 2])
4552 } else {
4553 Some((sorted[n / 2 - 1] + sorted[n / 2]) / 2.0)
4554 }
4555 }
4556 WindowStat::StdDev => {
4557 let n = values.len() as f64;
4558 let mean = values.iter().sum::<f64>() / n;
4559 let variance = values
4560 .iter()
4561 .map(|v| {
4562 let d = *v - mean;
4563 d * d
4564 })
4565 .sum::<f64>()
4566 / n;
4567 Some(variance.max(0.0).sqrt())
4568 }
4569 }
4570}
4571
4572impl std::fmt::Display for Raster {
4573 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4574 write!(
4575 f,
4576 "Raster({}×{}×{}, cell={:.6}, x=[{:.4},{:.4}], y=[{:.4},{:.4}], type={}, nodata={})",
4577 self.bands,
4578 self.cols,
4579 self.rows,
4580 self.cell_size_x,
4581 self.x_min,
4582 self.x_max(),
4583 self.y_min,
4584 self.y_max(),
4585 self.data_type,
4586 self.nodata,
4587 )
4588 }
4589}
4590
4591#[cfg(test)]
4592mod tests {
4593 use super::*;
4594 use flate2::{write::ZlibEncoder, Compression};
4595 use std::io::Write;
4596 use std::sync::{Arc, Mutex};
4597
4598 fn make_raster() -> Raster {
4599 let cfg = RasterConfig {
4600 cols: 4,
4601 rows: 3,
4602 cell_size: 10.0,
4603 nodata: -9999.0,
4604 ..Default::default()
4605 };
4606 let mut r = Raster::new(cfg);
4607 for row in 0..3 {
4608 for col in 0..4 {
4609 let _ = r.set(0, row, col, (row * 4 + col) as f64);
4610 }
4611 }
4612 r
4613 }
4614
4615 fn write_synthetic_hdf4_i16_fixture(file: &mut tempfile::NamedTempFile) {
4616 let mut bytes = vec![0x0E, 0x03, 0x13, 0x01];
4617 bytes.extend_from_slice(&[0x00, 0x01]);
4618 bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
4619 bytes.extend_from_slice(&[
4620 0x02, 0xBE, 0x00, 0x03, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x10,
4621 ]);
4622 bytes.resize(0x90, 0);
4623 bytes[0x80..0x90].copy_from_slice(&[
4624 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00,
4625 0x08, 0x00,
4626 ]);
4627 bytes.extend_from_slice(
4628 b"\nStructMetadata.0\nGridName=\"GridA\"\nXDim=4\nYDim=2\nUpperLeftPointMtrs=(0,10)\nLowerRightMtrs=(20,0)\nDataFieldName=\"FieldA\"\nDataType=DFNT_INT16\nDimList=(\"YDim\",\"XDim\")\n",
4629 );
4630 file.write_all(&bytes)
4631 .expect("synthetic HDF4 fixture should be writable");
4632 }
4633
4634 fn write_synthetic_hdf5_contiguous_fixture_header(
4635 bytes: &mut [u8],
4636 dataset_path: &str,
4637 payload_offset: usize,
4638 element_count: usize,
4639 bytes_per_value: usize,
4640 ) {
4641 const HEADER_OFFSET: usize = 256;
4642 const CONTINUATION_OFFSET: usize = 512;
4643 const CONTINUATION_SIZE: usize = 26;
4644
4645 let marker = dataset_path.as_bytes();
4646 bytes[64..64 + marker.len()].copy_from_slice(marker);
4647
4648 let payload_size = element_count * bytes_per_value;
4649
4650 let mut cursor = HEADER_OFFSET;
4651 bytes[cursor..cursor + 4].copy_from_slice(b"OHDR");
4652 cursor += 4;
4653 bytes[cursor] = 2;
4654 cursor += 1;
4655 bytes[cursor] = 0;
4656 cursor += 1;
4657 bytes[cursor] = 0x34;
4658 cursor += 1;
4659
4660 bytes[cursor..cursor + 4].copy_from_slice(&[0x01, 0x10, 0x00, 0x00]);
4661 cursor += 4;
4662 bytes[cursor..cursor + 8].copy_from_slice(&[0x02, 0x01, 0x00, 0x00, 0, 0, 0, 0]);
4663 cursor += 8;
4664 bytes[cursor..cursor + 8].copy_from_slice(&(element_count as u64).to_le_bytes());
4665 cursor += 8;
4666
4667 bytes[cursor..cursor + 4].copy_from_slice(&[0x03, 0x08, 0x00, 0x00]);
4668 cursor += 4;
4669 bytes[cursor] = 0x13;
4670 bytes[cursor + 1] = 0x00;
4671 bytes[cursor + 2] = 0x00;
4672 bytes[cursor + 3] = 0x00;
4673 bytes[cursor + 4..cursor + 8].copy_from_slice(&(bytes_per_value as u32).to_le_bytes());
4674 cursor += 8;
4675
4676 bytes[cursor..cursor + 4].copy_from_slice(&[0x10, 0x10, 0x00, 0x00]);
4677 cursor += 4;
4678 bytes[cursor..cursor + 8].copy_from_slice(&(CONTINUATION_OFFSET as u64).to_le_bytes());
4679 cursor += 8;
4680 bytes[cursor..cursor + 8].copy_from_slice(&(CONTINUATION_SIZE as u64).to_le_bytes());
4681
4682 let mut continuation_cursor = CONTINUATION_OFFSET;
4683 bytes[continuation_cursor..continuation_cursor + 4].copy_from_slice(b"OCHK");
4684 continuation_cursor += 4;
4685 bytes[continuation_cursor..continuation_cursor + 4]
4686 .copy_from_slice(&[0x08, 0x12, 0x00, 0x00]);
4687 continuation_cursor += 4;
4688 bytes[continuation_cursor] = 0x03;
4689 bytes[continuation_cursor + 1] = 0x01;
4690 continuation_cursor += 2;
4691 bytes[continuation_cursor..continuation_cursor + 8]
4692 .copy_from_slice(&(payload_offset as u64).to_le_bytes());
4693 continuation_cursor += 8;
4694 bytes[continuation_cursor..continuation_cursor + 8]
4695 .copy_from_slice(&(payload_size as u64).to_le_bytes());
4696 }
4697
4698 fn write_synthetic_hdf5_gedi_contiguous_fixture(file: &mut tempfile::NamedTempFile) {
4699 const OFFSET: usize = 321_111;
4700 const ELEMENTS: usize = 89_634;
4701
4702 let mut bytes = vec![0u8; OFFSET + ELEMENTS * 4];
4703 write_synthetic_hdf5_contiguous_fixture_header(
4704 &mut bytes,
4705 "/BEAM0000/elev_lowestmode",
4706 OFFSET,
4707 ELEMENTS,
4708 4,
4709 );
4710
4711 for i in 0..ELEMENTS {
4712 let v = i as f32;
4713 let start = OFFSET + i * 4;
4714 bytes[start..start + 4].copy_from_slice(&v.to_le_bytes());
4715 }
4716 file.write_all(&bytes)
4717 .expect("synthetic HDF5 fixture should be writable");
4718 }
4719
4720 fn write_synthetic_hdf5_viirs_xdim_fixture(file: &mut tempfile::NamedTempFile) {
4721 const OFFSET: usize = 45_321;
4722 const ELEMENTS: usize = 2_400;
4723
4724 let mut bytes = vec![0u8; OFFSET + ELEMENTS * 8];
4725 write_synthetic_hdf5_contiguous_fixture_header(
4726 &mut bytes,
4727 "/HDFEOS/GRIDS/VIIRS_Grid_8Day_VI_500m/XDim",
4728 OFFSET,
4729 ELEMENTS,
4730 8,
4731 );
4732
4733 for i in 0..ELEMENTS {
4734 let v = i as f64 + 0.5;
4735 let start = OFFSET + i * 8;
4736 bytes[start..start + 8].copy_from_slice(&v.to_le_bytes());
4737 }
4738 file.write_all(&bytes)
4739 .expect("synthetic HDF5 VIIRS fixture should be writable");
4740 }
4741
4742 fn write_synthetic_hdf5_generic_contiguous_f32_fixture(file: &mut tempfile::NamedTempFile) {
4743 const OFFSET: usize = 62_222;
4744 const ELEMENTS: usize = 12;
4745 const DATASET_PATH: &str = "/ScienceData/NDVI";
4746
4747 let mut bytes = vec![0u8; OFFSET + ELEMENTS * 4];
4748 write_synthetic_hdf5_contiguous_fixture_header(
4749 &mut bytes,
4750 DATASET_PATH,
4751 OFFSET,
4752 ELEMENTS,
4753 4,
4754 );
4755
4756 for i in 0..ELEMENTS {
4757 let v = (i as f32) * 0.25;
4758 let start = OFFSET + i * 4;
4759 bytes[start..start + 4].copy_from_slice(&v.to_le_bytes());
4760 }
4761 file.write_all(&bytes)
4762 .expect("synthetic HDF5 generic fixture should be writable");
4763 }
4764
4765 fn write_synthetic_hdf5_chunked_single_chunk_f32_fixture(file: &mut tempfile::NamedTempFile) {
4766 const DATASET_PATH: &str = "/ScienceData/NDVI_Chunked";
4767 const HEADER_OFFSET: usize = 1024;
4768 const CHUNK_INDEX_OFFSET: usize = 4096;
4769 const PAYLOAD_OFFSET: usize = 8192;
4770 const ROWS: usize = 2;
4771 const COLS: usize = 3;
4772
4773 let values = [0.5f32, 1.5, 2.5, 3.5, 4.5, 5.5];
4774 let mut raw_payload = Vec::<u8>::new();
4775 for value in values {
4776 raw_payload.extend_from_slice(&value.to_le_bytes());
4777 }
4778
4779 let total_len = PAYLOAD_OFFSET + raw_payload.len();
4780 let mut bytes = vec![0u8; total_len];
4781
4782 let marker = DATASET_PATH.as_bytes();
4783 bytes[64..64 + marker.len()].copy_from_slice(marker);
4784
4785 let mut cursor = HEADER_OFFSET;
4787 bytes[cursor] = 1;
4788 bytes[cursor + 1] = 0;
4789 bytes[cursor + 2..cursor + 4].copy_from_slice(&(3u16).to_le_bytes());
4790 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
4791 bytes[cursor + 8..cursor + 12].copy_from_slice(&(95u32).to_le_bytes());
4792 bytes[cursor + 12..cursor + 16].copy_from_slice(&0u32.to_le_bytes());
4793 cursor += 16;
4794
4795 bytes[cursor..cursor + 2].copy_from_slice(&(0x0001u16).to_le_bytes());
4797 bytes[cursor + 2..cursor + 4].copy_from_slice(&(24u16).to_le_bytes());
4798 bytes[cursor + 4] = 0;
4799 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
4800 cursor += 8;
4801 bytes[cursor] = 2;
4802 bytes[cursor + 1] = 2;
4803 bytes[cursor + 2] = 0;
4804 bytes[cursor + 3] = 0;
4805 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
4806 bytes[cursor + 8..cursor + 16].copy_from_slice(&(ROWS as u64).to_le_bytes());
4807 bytes[cursor + 16..cursor + 24].copy_from_slice(&(COLS as u64).to_le_bytes());
4808 cursor += 24;
4809
4810 bytes[cursor..cursor + 2].copy_from_slice(&(0x0003u16).to_le_bytes());
4812 bytes[cursor + 2..cursor + 4].copy_from_slice(&(8u16).to_le_bytes());
4813 bytes[cursor + 4] = 0;
4814 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
4815 cursor += 8;
4816 bytes[cursor] = 0x13;
4817 bytes[cursor + 1] = 0;
4818 bytes[cursor + 2] = 0;
4819 bytes[cursor + 3] = 0;
4820 bytes[cursor + 4..cursor + 8].copy_from_slice(&(4u32).to_le_bytes());
4821 cursor += 8;
4822
4823 bytes[cursor..cursor + 2].copy_from_slice(&(0x0008u16).to_le_bytes());
4825 bytes[cursor + 2..cursor + 4].copy_from_slice(&(19u16).to_le_bytes());
4826 bytes[cursor + 4] = 0;
4827 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
4828 cursor += 8;
4829 bytes[cursor] = 3;
4830 bytes[cursor + 1] = 2;
4831 bytes[cursor + 2] = 2;
4832 bytes[cursor + 3..cursor + 11].copy_from_slice(&(CHUNK_INDEX_OFFSET as u64).to_le_bytes());
4833 bytes[cursor + 11..cursor + 15].copy_from_slice(&(ROWS as u32).to_le_bytes());
4834 bytes[cursor + 15..cursor + 19].copy_from_slice(&(COLS as u32).to_le_bytes());
4835
4836 let mut node_cursor = CHUNK_INDEX_OFFSET;
4838 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
4839 bytes[node_cursor + 4] = 1;
4840 bytes[node_cursor + 5] = 0;
4841 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(1u16).to_le_bytes());
4842 node_cursor += 24;
4843 bytes[node_cursor..node_cursor + 8]
4844 .copy_from_slice(&(raw_payload.len() as u64).to_le_bytes());
4845 node_cursor += 8;
4846 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
4847 node_cursor += 8;
4848 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
4849 node_cursor += 8;
4850 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(PAYLOAD_OFFSET as u64).to_le_bytes());
4851
4852 bytes[PAYLOAD_OFFSET..PAYLOAD_OFFSET + raw_payload.len()].copy_from_slice(&raw_payload);
4854
4855 file.write_all(&bytes)
4856 .expect("synthetic HDF5 chunked fixture should be writable");
4857 }
4858
4859 fn write_synthetic_hdf5_chunked_two_chunk_f32_fixture(file: &mut tempfile::NamedTempFile) {
4860 const DATASET_PATH: &str = "/ScienceData/NDVI_Chunked_Two";
4861 const HEADER_OFFSET: usize = 1024;
4862 const CHUNK_INDEX_OFFSET: usize = 4096;
4863 const PAYLOAD_A_OFFSET: usize = 8192;
4864 const PAYLOAD_B_OFFSET: usize = 8208;
4865 const ROWS: usize = 2;
4866 const COLS: usize = 4;
4867 const CHUNK_ROWS: usize = 2;
4868 const CHUNK_COLS: usize = 2;
4869
4870 let values_a = [0.5f32, 1.5, 4.5, 5.5];
4871 let values_b = [2.5f32, 3.5, 6.5, 7.5];
4872 let mut payload_a = Vec::<u8>::new();
4873 let mut payload_b = Vec::<u8>::new();
4874 for value in values_a {
4875 payload_a.extend_from_slice(&value.to_le_bytes());
4876 }
4877 for value in values_b {
4878 payload_b.extend_from_slice(&value.to_le_bytes());
4879 }
4880
4881 let total_len = PAYLOAD_B_OFFSET + payload_b.len();
4882 let mut bytes = vec![0u8; total_len];
4883 let marker = DATASET_PATH.as_bytes();
4884 bytes[64..64 + marker.len()].copy_from_slice(marker);
4885
4886 let mut cursor = HEADER_OFFSET;
4887 bytes[cursor] = 1;
4888 bytes[cursor + 1] = 0;
4889 bytes[cursor + 2..cursor + 4].copy_from_slice(&(3u16).to_le_bytes());
4890 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
4891 bytes[cursor + 8..cursor + 12].copy_from_slice(&(95u32).to_le_bytes());
4892 bytes[cursor + 12..cursor + 16].copy_from_slice(&0u32.to_le_bytes());
4893 cursor += 16;
4894
4895 bytes[cursor..cursor + 2].copy_from_slice(&(0x0001u16).to_le_bytes());
4896 bytes[cursor + 2..cursor + 4].copy_from_slice(&(24u16).to_le_bytes());
4897 bytes[cursor + 4] = 0;
4898 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
4899 cursor += 8;
4900 bytes[cursor] = 2;
4901 bytes[cursor + 1] = 2;
4902 bytes[cursor + 2] = 0;
4903 bytes[cursor + 3] = 0;
4904 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
4905 bytes[cursor + 8..cursor + 16].copy_from_slice(&(ROWS as u64).to_le_bytes());
4906 bytes[cursor + 16..cursor + 24].copy_from_slice(&(COLS as u64).to_le_bytes());
4907 cursor += 24;
4908
4909 bytes[cursor..cursor + 2].copy_from_slice(&(0x0003u16).to_le_bytes());
4910 bytes[cursor + 2..cursor + 4].copy_from_slice(&(8u16).to_le_bytes());
4911 bytes[cursor + 4] = 0;
4912 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
4913 cursor += 8;
4914 bytes[cursor] = 0x13;
4915 bytes[cursor + 1] = 0;
4916 bytes[cursor + 2] = 0;
4917 bytes[cursor + 3] = 0;
4918 bytes[cursor + 4..cursor + 8].copy_from_slice(&(4u32).to_le_bytes());
4919 cursor += 8;
4920
4921 bytes[cursor..cursor + 2].copy_from_slice(&(0x0008u16).to_le_bytes());
4922 bytes[cursor + 2..cursor + 4].copy_from_slice(&(19u16).to_le_bytes());
4923 bytes[cursor + 4] = 0;
4924 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
4925 cursor += 8;
4926 bytes[cursor] = 3;
4927 bytes[cursor + 1] = 2;
4928 bytes[cursor + 2] = 2;
4929 bytes[cursor + 3..cursor + 11].copy_from_slice(&(CHUNK_INDEX_OFFSET as u64).to_le_bytes());
4930 bytes[cursor + 11..cursor + 15].copy_from_slice(&(CHUNK_ROWS as u32).to_le_bytes());
4931 bytes[cursor + 15..cursor + 19].copy_from_slice(&(CHUNK_COLS as u32).to_le_bytes());
4932
4933 let mut node_cursor = CHUNK_INDEX_OFFSET;
4934 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
4935 bytes[node_cursor + 4] = 1;
4936 bytes[node_cursor + 5] = 0;
4937 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(2u16).to_le_bytes());
4938 node_cursor += 24;
4939
4940 bytes[node_cursor..node_cursor + 8]
4941 .copy_from_slice(&(payload_a.len() as u64).to_le_bytes());
4942 node_cursor += 8;
4943 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
4944 node_cursor += 8;
4945 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
4946 node_cursor += 8;
4947 bytes[node_cursor..node_cursor + 8]
4948 .copy_from_slice(&(PAYLOAD_A_OFFSET as u64).to_le_bytes());
4949 node_cursor += 8;
4950
4951 bytes[node_cursor..node_cursor + 8]
4952 .copy_from_slice(&(payload_b.len() as u64).to_le_bytes());
4953 node_cursor += 8;
4954 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
4955 node_cursor += 8;
4956 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
4957 node_cursor += 8;
4958 bytes[node_cursor..node_cursor + 8]
4959 .copy_from_slice(&(PAYLOAD_B_OFFSET as u64).to_le_bytes());
4960
4961 bytes[PAYLOAD_A_OFFSET..PAYLOAD_A_OFFSET + payload_a.len()].copy_from_slice(&payload_a);
4962 bytes[PAYLOAD_B_OFFSET..PAYLOAD_B_OFFSET + payload_b.len()].copy_from_slice(&payload_b);
4963
4964 file.write_all(&bytes)
4965 .expect("synthetic HDF5 two-chunk fixture should be writable");
4966 }
4967
4968 fn write_synthetic_hdf5_chunked_two_chunk_f32_deflate_fixture(
4969 file: &mut tempfile::NamedTempFile,
4970 ) {
4971 const DATASET_PATH: &str = "/ScienceData/NDVI_Chunked_Two_Deflate";
4972 const HEADER_OFFSET: usize = 1024;
4973 const CHUNK_INDEX_OFFSET: usize = 4096;
4974 const PAYLOAD_A_OFFSET: usize = 8192;
4975 const ROWS: usize = 2;
4976 const COLS: usize = 4;
4977 const CHUNK_ROWS: usize = 2;
4978 const CHUNK_COLS: usize = 2;
4979
4980 let values_a = [0.5f32, 1.5, 4.5, 5.5];
4981 let values_b = [2.5f32, 3.5, 6.5, 7.5];
4982 let payload_a = encode_f32_values_as_zlib(&values_a);
4983 let payload_b = encode_f32_values_as_zlib(&values_b);
4984 let payload_b_offset = PAYLOAD_A_OFFSET + payload_a.len();
4985
4986 let total_len = payload_b_offset + payload_b.len();
4987 let mut bytes = vec![0u8; total_len];
4988 let marker = DATASET_PATH.as_bytes();
4989 bytes[64..64 + marker.len()].copy_from_slice(marker);
4990
4991 let mut cursor = HEADER_OFFSET;
4992 bytes[cursor] = 1;
4993 bytes[cursor + 1] = 0;
4994 bytes[cursor + 2..cursor + 4].copy_from_slice(&(4u16).to_le_bytes());
4995 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
4996 bytes[cursor + 8..cursor + 12].copy_from_slice(&(121u32).to_le_bytes());
4997 bytes[cursor + 12..cursor + 16].copy_from_slice(&0u32.to_le_bytes());
4998 cursor += 16;
4999
5000 bytes[cursor..cursor + 2].copy_from_slice(&(0x0001u16).to_le_bytes());
5001 bytes[cursor + 2..cursor + 4].copy_from_slice(&(24u16).to_le_bytes());
5002 bytes[cursor + 4] = 0;
5003 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5004 cursor += 8;
5005 bytes[cursor] = 2;
5006 bytes[cursor + 1] = 2;
5007 bytes[cursor + 2] = 0;
5008 bytes[cursor + 3] = 0;
5009 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5010 bytes[cursor + 8..cursor + 16].copy_from_slice(&(ROWS as u64).to_le_bytes());
5011 bytes[cursor + 16..cursor + 24].copy_from_slice(&(COLS as u64).to_le_bytes());
5012 cursor += 24;
5013
5014 bytes[cursor..cursor + 2].copy_from_slice(&(0x0003u16).to_le_bytes());
5015 bytes[cursor + 2..cursor + 4].copy_from_slice(&(8u16).to_le_bytes());
5016 bytes[cursor + 4] = 0;
5017 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5018 cursor += 8;
5019 bytes[cursor] = 0x13;
5020 bytes[cursor + 1] = 0;
5021 bytes[cursor + 2] = 0;
5022 bytes[cursor + 3] = 0;
5023 bytes[cursor + 4..cursor + 8].copy_from_slice(&(4u32).to_le_bytes());
5024 cursor += 8;
5025
5026 bytes[cursor..cursor + 2].copy_from_slice(&(0x0008u16).to_le_bytes());
5027 bytes[cursor + 2..cursor + 4].copy_from_slice(&(19u16).to_le_bytes());
5028 bytes[cursor + 4] = 0;
5029 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5030 cursor += 8;
5031 bytes[cursor] = 3;
5032 bytes[cursor + 1] = 2;
5033 bytes[cursor + 2] = 2;
5034 bytes[cursor + 3..cursor + 11].copy_from_slice(&(CHUNK_INDEX_OFFSET as u64).to_le_bytes());
5035 bytes[cursor + 11..cursor + 15].copy_from_slice(&(CHUNK_ROWS as u32).to_le_bytes());
5036 bytes[cursor + 15..cursor + 19].copy_from_slice(&(CHUNK_COLS as u32).to_le_bytes());
5037 cursor += 19;
5038
5039 bytes[cursor..cursor + 2].copy_from_slice(&(0x000bu16).to_le_bytes());
5040 bytes[cursor + 2..cursor + 4].copy_from_slice(&(16u16).to_le_bytes());
5041 bytes[cursor + 4] = 0;
5042 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5043 cursor += 8;
5044 bytes[cursor] = 1;
5045 bytes[cursor + 1] = 1;
5046 bytes[cursor + 2..cursor + 8].copy_from_slice(&[0, 0, 0, 0, 0, 0]);
5047 cursor += 8;
5048 bytes[cursor..cursor + 2].copy_from_slice(&(1u16).to_le_bytes());
5049 bytes[cursor + 2..cursor + 4].copy_from_slice(&(0u16).to_le_bytes());
5050 bytes[cursor + 4..cursor + 6].copy_from_slice(&(0u16).to_le_bytes());
5051 bytes[cursor + 6..cursor + 8].copy_from_slice(&(0u16).to_le_bytes());
5052
5053 let mut node_cursor = CHUNK_INDEX_OFFSET;
5054 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
5055 bytes[node_cursor + 4] = 1;
5056 bytes[node_cursor + 5] = 0;
5057 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(2u16).to_le_bytes());
5058 node_cursor += 24;
5059
5060 bytes[node_cursor..node_cursor + 8]
5061 .copy_from_slice(&(payload_a.len() as u64).to_le_bytes());
5062 node_cursor += 8;
5063 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5064 node_cursor += 8;
5065 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5066 node_cursor += 8;
5067 bytes[node_cursor..node_cursor + 8]
5068 .copy_from_slice(&(PAYLOAD_A_OFFSET as u64).to_le_bytes());
5069 node_cursor += 8;
5070
5071 bytes[node_cursor..node_cursor + 8]
5072 .copy_from_slice(&(payload_b.len() as u64).to_le_bytes());
5073 node_cursor += 8;
5074 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5075 node_cursor += 8;
5076 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5077 node_cursor += 8;
5078 bytes[node_cursor..node_cursor + 8]
5079 .copy_from_slice(&(payload_b_offset as u64).to_le_bytes());
5080
5081 bytes[PAYLOAD_A_OFFSET..PAYLOAD_A_OFFSET + payload_a.len()].copy_from_slice(&payload_a);
5082 bytes[payload_b_offset..payload_b_offset + payload_b.len()].copy_from_slice(&payload_b);
5083
5084 file.write_all(&bytes)
5085 .expect("synthetic HDF5 deflate two-chunk fixture should be writable");
5086 }
5087
5088 fn write_synthetic_hdf5_chunked_two_leaf_f32_fixture(file: &mut tempfile::NamedTempFile) {
5089 const DATASET_PATH: &str = "/ScienceData/NDVI_Chunked_Two_Leaf";
5090 const HEADER_OFFSET: usize = 1024;
5091 const FIRST_LEAF_OFFSET: usize = 4096;
5092 const SECOND_LEAF_OFFSET: usize = 4184;
5093 const PAYLOAD_A_OFFSET: usize = 8192;
5094 const PAYLOAD_B_OFFSET: usize = 8208;
5095 const ROWS: usize = 2;
5096 const COLS: usize = 4;
5097 const CHUNK_ROWS: usize = 2;
5098 const CHUNK_COLS: usize = 2;
5099
5100 let values_a = [0.5f32, 1.5, 4.5, 5.5];
5101 let values_b = [2.5f32, 3.5, 6.5, 7.5];
5102 let mut payload_a = Vec::<u8>::new();
5103 let mut payload_b = Vec::<u8>::new();
5104 for value in values_a {
5105 payload_a.extend_from_slice(&value.to_le_bytes());
5106 }
5107 for value in values_b {
5108 payload_b.extend_from_slice(&value.to_le_bytes());
5109 }
5110
5111 let total_len = PAYLOAD_B_OFFSET + payload_b.len();
5112 let mut bytes = vec![0u8; total_len];
5113 let marker = DATASET_PATH.as_bytes();
5114 bytes[64..64 + marker.len()].copy_from_slice(marker);
5115
5116 let mut cursor = HEADER_OFFSET;
5117 bytes[cursor] = 1;
5118 bytes[cursor + 1] = 0;
5119 bytes[cursor + 2..cursor + 4].copy_from_slice(&(3u16).to_le_bytes());
5120 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5121 bytes[cursor + 8..cursor + 12].copy_from_slice(&(95u32).to_le_bytes());
5122 bytes[cursor + 12..cursor + 16].copy_from_slice(&0u32.to_le_bytes());
5123 cursor += 16;
5124
5125 bytes[cursor..cursor + 2].copy_from_slice(&(0x0001u16).to_le_bytes());
5126 bytes[cursor + 2..cursor + 4].copy_from_slice(&(24u16).to_le_bytes());
5127 bytes[cursor + 4] = 0;
5128 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5129 cursor += 8;
5130 bytes[cursor] = 2;
5131 bytes[cursor + 1] = 2;
5132 bytes[cursor + 2] = 0;
5133 bytes[cursor + 3] = 0;
5134 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5135 bytes[cursor + 8..cursor + 16].copy_from_slice(&(ROWS as u64).to_le_bytes());
5136 bytes[cursor + 16..cursor + 24].copy_from_slice(&(COLS as u64).to_le_bytes());
5137 cursor += 24;
5138
5139 bytes[cursor..cursor + 2].copy_from_slice(&(0x0003u16).to_le_bytes());
5140 bytes[cursor + 2..cursor + 4].copy_from_slice(&(8u16).to_le_bytes());
5141 bytes[cursor + 4] = 0;
5142 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5143 cursor += 8;
5144 bytes[cursor] = 0x13;
5145 bytes[cursor + 1] = 0;
5146 bytes[cursor + 2] = 0;
5147 bytes[cursor + 3] = 0;
5148 bytes[cursor + 4..cursor + 8].copy_from_slice(&(4u32).to_le_bytes());
5149 cursor += 8;
5150
5151 bytes[cursor..cursor + 2].copy_from_slice(&(0x0008u16).to_le_bytes());
5152 bytes[cursor + 2..cursor + 4].copy_from_slice(&(19u16).to_le_bytes());
5153 bytes[cursor + 4] = 0;
5154 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5155 cursor += 8;
5156 bytes[cursor] = 3;
5157 bytes[cursor + 1] = 2;
5158 bytes[cursor + 2] = 2;
5159 bytes[cursor + 3..cursor + 11].copy_from_slice(&(FIRST_LEAF_OFFSET as u64).to_le_bytes());
5160 bytes[cursor + 11..cursor + 15].copy_from_slice(&(CHUNK_ROWS as u32).to_le_bytes());
5161 bytes[cursor + 15..cursor + 19].copy_from_slice(&(CHUNK_COLS as u32).to_le_bytes());
5162
5163 let mut node_cursor = FIRST_LEAF_OFFSET;
5164 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
5165 bytes[node_cursor + 4] = 1;
5166 bytes[node_cursor + 5] = 0;
5167 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(1u16).to_le_bytes());
5168 bytes[node_cursor + 8..node_cursor + 16].copy_from_slice(&u64::MAX.to_le_bytes());
5169 bytes[node_cursor + 16..node_cursor + 24]
5170 .copy_from_slice(&(SECOND_LEAF_OFFSET as u64).to_le_bytes());
5171 node_cursor += 24;
5172 bytes[node_cursor..node_cursor + 8]
5173 .copy_from_slice(&(payload_a.len() as u64).to_le_bytes());
5174 node_cursor += 8;
5175 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5176 node_cursor += 8;
5177 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5178 node_cursor += 8;
5179 bytes[node_cursor..node_cursor + 8]
5180 .copy_from_slice(&(PAYLOAD_A_OFFSET as u64).to_le_bytes());
5181
5182 let mut node_cursor = SECOND_LEAF_OFFSET;
5183 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
5184 bytes[node_cursor + 4] = 1;
5185 bytes[node_cursor + 5] = 0;
5186 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(1u16).to_le_bytes());
5187 bytes[node_cursor + 8..node_cursor + 16]
5188 .copy_from_slice(&(FIRST_LEAF_OFFSET as u64).to_le_bytes());
5189 bytes[node_cursor + 16..node_cursor + 24].copy_from_slice(&u64::MAX.to_le_bytes());
5190 node_cursor += 24;
5191 bytes[node_cursor..node_cursor + 8]
5192 .copy_from_slice(&(payload_b.len() as u64).to_le_bytes());
5193 node_cursor += 8;
5194 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5195 node_cursor += 8;
5196 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5197 node_cursor += 8;
5198 bytes[node_cursor..node_cursor + 8]
5199 .copy_from_slice(&(PAYLOAD_B_OFFSET as u64).to_le_bytes());
5200
5201 bytes[PAYLOAD_A_OFFSET..PAYLOAD_A_OFFSET + payload_a.len()].copy_from_slice(&payload_a);
5202 bytes[PAYLOAD_B_OFFSET..PAYLOAD_B_OFFSET + payload_b.len()].copy_from_slice(&payload_b);
5203
5204 file.write_all(&bytes)
5205 .expect("synthetic HDF5 two-leaf fixture should be writable");
5206 }
5207
5208 fn write_synthetic_hdf5_chunked_internal_root_fixture(file: &mut tempfile::NamedTempFile) {
5209 const DATASET_PATH: &str = "/ScienceData/NDVI_Chunked_InternalRoot";
5210 const HEADER_OFFSET: usize = 1024;
5211 const INTERNAL_ROOT_OFFSET: usize = 4096;
5212 const FIRST_LEAF_OFFSET: usize = 4168;
5213 const SECOND_LEAF_OFFSET: usize = 4256;
5214 const PAYLOAD_A_OFFSET: usize = 8192;
5215 const PAYLOAD_B_OFFSET: usize = 8208;
5216 const ROWS: usize = 2;
5217 const COLS: usize = 4;
5218 const CHUNK_ROWS: usize = 2;
5219 const CHUNK_COLS: usize = 2;
5220
5221 let values_a = [0.5f32, 1.5, 4.5, 5.5];
5222 let values_b = [2.5f32, 3.5, 6.5, 7.5];
5223 let mut payload_a = Vec::<u8>::new();
5224 let mut payload_b = Vec::<u8>::new();
5225 for value in values_a {
5226 payload_a.extend_from_slice(&value.to_le_bytes());
5227 }
5228 for value in values_b {
5229 payload_b.extend_from_slice(&value.to_le_bytes());
5230 }
5231
5232 let total_len = PAYLOAD_B_OFFSET + payload_b.len();
5233 let mut bytes = vec![0u8; total_len];
5234 let marker = DATASET_PATH.as_bytes();
5235 bytes[64..64 + marker.len()].copy_from_slice(marker);
5236
5237 let mut cursor = HEADER_OFFSET;
5238 bytes[cursor] = 1;
5239 bytes[cursor + 1] = 0;
5240 bytes[cursor + 2..cursor + 4].copy_from_slice(&(3u16).to_le_bytes());
5241 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5242 bytes[cursor + 8..cursor + 12].copy_from_slice(&(95u32).to_le_bytes());
5243 bytes[cursor + 12..cursor + 16].copy_from_slice(&0u32.to_le_bytes());
5244 cursor += 16;
5245
5246 bytes[cursor..cursor + 2].copy_from_slice(&(0x0001u16).to_le_bytes());
5247 bytes[cursor + 2..cursor + 4].copy_from_slice(&(24u16).to_le_bytes());
5248 bytes[cursor + 4] = 0;
5249 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5250 cursor += 8;
5251 bytes[cursor] = 2;
5252 bytes[cursor + 1] = 2;
5253 bytes[cursor + 2] = 0;
5254 bytes[cursor + 3] = 0;
5255 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5256 bytes[cursor + 8..cursor + 16].copy_from_slice(&(ROWS as u64).to_le_bytes());
5257 bytes[cursor + 16..cursor + 24].copy_from_slice(&(COLS as u64).to_le_bytes());
5258 cursor += 24;
5259
5260 bytes[cursor..cursor + 2].copy_from_slice(&(0x0003u16).to_le_bytes());
5261 bytes[cursor + 2..cursor + 4].copy_from_slice(&(8u16).to_le_bytes());
5262 bytes[cursor + 4] = 0;
5263 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5264 cursor += 8;
5265 bytes[cursor] = 0x13;
5266 bytes[cursor + 1] = 0;
5267 bytes[cursor + 2] = 0;
5268 bytes[cursor + 3] = 0;
5269 bytes[cursor + 4..cursor + 8].copy_from_slice(&(4u32).to_le_bytes());
5270 cursor += 8;
5271
5272 bytes[cursor..cursor + 2].copy_from_slice(&(0x0008u16).to_le_bytes());
5273 bytes[cursor + 2..cursor + 4].copy_from_slice(&(19u16).to_le_bytes());
5274 bytes[cursor + 4] = 0;
5275 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5276 cursor += 8;
5277 bytes[cursor] = 3;
5278 bytes[cursor + 1] = 2;
5279 bytes[cursor + 2] = 2;
5280 bytes[cursor + 3..cursor + 11]
5281 .copy_from_slice(&(INTERNAL_ROOT_OFFSET as u64).to_le_bytes());
5282 bytes[cursor + 11..cursor + 15].copy_from_slice(&(CHUNK_ROWS as u32).to_le_bytes());
5283 bytes[cursor + 15..cursor + 19].copy_from_slice(&(CHUNK_COLS as u32).to_le_bytes());
5284
5285 bytes[INTERNAL_ROOT_OFFSET..INTERNAL_ROOT_OFFSET + 4].copy_from_slice(b"TREE");
5286 bytes[INTERNAL_ROOT_OFFSET + 4] = 1;
5287 bytes[INTERNAL_ROOT_OFFSET + 5] = 1;
5288 bytes[INTERNAL_ROOT_OFFSET + 6..INTERNAL_ROOT_OFFSET + 8]
5289 .copy_from_slice(&(2u16).to_le_bytes());
5290 bytes[INTERNAL_ROOT_OFFSET + 8..INTERNAL_ROOT_OFFSET + 16]
5291 .copy_from_slice(&u64::MAX.to_le_bytes());
5292 bytes[INTERNAL_ROOT_OFFSET + 16..INTERNAL_ROOT_OFFSET + 24]
5293 .copy_from_slice(&u64::MAX.to_le_bytes());
5294
5295 let mut node_cursor = INTERNAL_ROOT_OFFSET + 24;
5296 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5297 node_cursor += 8;
5298 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5299 node_cursor += 8;
5300 bytes[node_cursor..node_cursor + 8]
5301 .copy_from_slice(&(FIRST_LEAF_OFFSET as u64).to_le_bytes());
5302 node_cursor += 8;
5303 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5304 node_cursor += 8;
5305 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(4u64).to_le_bytes());
5306 node_cursor += 8;
5307 bytes[node_cursor..node_cursor + 8]
5308 .copy_from_slice(&(SECOND_LEAF_OFFSET as u64).to_le_bytes());
5309
5310 let mut node_cursor = FIRST_LEAF_OFFSET;
5311 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
5312 bytes[node_cursor + 4] = 1;
5313 bytes[node_cursor + 5] = 0;
5314 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(1u16).to_le_bytes());
5315 bytes[node_cursor + 8..node_cursor + 16].copy_from_slice(&u64::MAX.to_le_bytes());
5316 bytes[node_cursor + 16..node_cursor + 24].copy_from_slice(&u64::MAX.to_le_bytes());
5317 node_cursor += 24;
5318 bytes[node_cursor..node_cursor + 8]
5319 .copy_from_slice(&(payload_a.len() as u64).to_le_bytes());
5320 node_cursor += 8;
5321 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5322 node_cursor += 8;
5323 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5324 node_cursor += 8;
5325 bytes[node_cursor..node_cursor + 8]
5326 .copy_from_slice(&(PAYLOAD_A_OFFSET as u64).to_le_bytes());
5327
5328 let mut node_cursor = SECOND_LEAF_OFFSET;
5329 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
5330 bytes[node_cursor + 4] = 1;
5331 bytes[node_cursor + 5] = 0;
5332 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(1u16).to_le_bytes());
5333 bytes[node_cursor + 8..node_cursor + 16].copy_from_slice(&u64::MAX.to_le_bytes());
5334 bytes[node_cursor + 16..node_cursor + 24].copy_from_slice(&u64::MAX.to_le_bytes());
5335 node_cursor += 24;
5336 bytes[node_cursor..node_cursor + 8]
5337 .copy_from_slice(&(payload_b.len() as u64).to_le_bytes());
5338 node_cursor += 8;
5339 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5340 node_cursor += 8;
5341 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5342 node_cursor += 8;
5343 bytes[node_cursor..node_cursor + 8]
5344 .copy_from_slice(&(PAYLOAD_B_OFFSET as u64).to_le_bytes());
5345
5346 bytes[PAYLOAD_A_OFFSET..PAYLOAD_A_OFFSET + payload_a.len()].copy_from_slice(&payload_a);
5347 bytes[PAYLOAD_B_OFFSET..PAYLOAD_B_OFFSET + payload_b.len()].copy_from_slice(&payload_b);
5348
5349 file.write_all(&bytes)
5350 .expect("synthetic HDF5 internal-root fixture should be writable");
5351 }
5352
5353 fn write_synthetic_hdf5_chunked_multilevel_root_fixture(file: &mut tempfile::NamedTempFile) {
5354 const DATASET_PATH: &str = "/ScienceData/NDVI_Chunked_MultiLevelRoot";
5355 const HEADER_OFFSET: usize = 1024;
5356 const ROOT_OFFSET: usize = 4096;
5357 const FIRST_INTERNAL_OFFSET: usize = 4168;
5358 const SECOND_INTERNAL_OFFSET: usize = 4256;
5359 const FIRST_LEAF_OFFSET: usize = 4344;
5360 const SECOND_LEAF_OFFSET: usize = 4432;
5361 const THIRD_LEAF_OFFSET: usize = 4520;
5362 const PAYLOAD_A_OFFSET: usize = 8192;
5363 const PAYLOAD_B_OFFSET: usize = 8208;
5364 const PAYLOAD_C_OFFSET: usize = 8224;
5365 const ROWS: usize = 2;
5366 const COLS: usize = 6;
5367 const CHUNK_ROWS: usize = 2;
5368 const CHUNK_COLS: usize = 2;
5369
5370 let values_a = [0.5f32, 1.5, 4.5, 5.5];
5371 let values_b = [2.5f32, 3.5, 6.5, 7.5];
5372 let values_c = [8.5f32, 9.5, 10.5, 11.5];
5373 let mut payload_a = Vec::<u8>::new();
5374 let mut payload_b = Vec::<u8>::new();
5375 let mut payload_c = Vec::<u8>::new();
5376 for value in values_a {
5377 payload_a.extend_from_slice(&value.to_le_bytes());
5378 }
5379 for value in values_b {
5380 payload_b.extend_from_slice(&value.to_le_bytes());
5381 }
5382 for value in values_c {
5383 payload_c.extend_from_slice(&value.to_le_bytes());
5384 }
5385
5386 let total_len = PAYLOAD_C_OFFSET + payload_c.len();
5387 let mut bytes = vec![0u8; total_len];
5388 let marker = DATASET_PATH.as_bytes();
5389 bytes[64..64 + marker.len()].copy_from_slice(marker);
5390
5391 let mut cursor = HEADER_OFFSET;
5392 bytes[cursor] = 1;
5393 bytes[cursor + 1] = 0;
5394 bytes[cursor + 2..cursor + 4].copy_from_slice(&(3u16).to_le_bytes());
5395 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5396 bytes[cursor + 8..cursor + 12].copy_from_slice(&(95u32).to_le_bytes());
5397 bytes[cursor + 12..cursor + 16].copy_from_slice(&0u32.to_le_bytes());
5398 cursor += 16;
5399
5400 bytes[cursor..cursor + 2].copy_from_slice(&(0x0001u16).to_le_bytes());
5401 bytes[cursor + 2..cursor + 4].copy_from_slice(&(24u16).to_le_bytes());
5402 bytes[cursor + 4] = 0;
5403 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5404 cursor += 8;
5405 bytes[cursor] = 2;
5406 bytes[cursor + 1] = 2;
5407 bytes[cursor + 2] = 0;
5408 bytes[cursor + 3] = 0;
5409 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5410 bytes[cursor + 8..cursor + 16].copy_from_slice(&(ROWS as u64).to_le_bytes());
5411 bytes[cursor + 16..cursor + 24].copy_from_slice(&(COLS as u64).to_le_bytes());
5412 cursor += 24;
5413
5414 bytes[cursor..cursor + 2].copy_from_slice(&(0x0003u16).to_le_bytes());
5415 bytes[cursor + 2..cursor + 4].copy_from_slice(&(8u16).to_le_bytes());
5416 bytes[cursor + 4] = 0;
5417 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5418 cursor += 8;
5419 bytes[cursor] = 0x13;
5420 bytes[cursor + 1] = 0;
5421 bytes[cursor + 2] = 0;
5422 bytes[cursor + 3] = 0;
5423 bytes[cursor + 4..cursor + 8].copy_from_slice(&(4u32).to_le_bytes());
5424 cursor += 8;
5425
5426 bytes[cursor..cursor + 2].copy_from_slice(&(0x0008u16).to_le_bytes());
5427 bytes[cursor + 2..cursor + 4].copy_from_slice(&(19u16).to_le_bytes());
5428 bytes[cursor + 4] = 0;
5429 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5430 cursor += 8;
5431 bytes[cursor] = 3;
5432 bytes[cursor + 1] = 2;
5433 bytes[cursor + 2] = 2;
5434 bytes[cursor + 3..cursor + 11].copy_from_slice(&(ROOT_OFFSET as u64).to_le_bytes());
5435 bytes[cursor + 11..cursor + 15].copy_from_slice(&(CHUNK_ROWS as u32).to_le_bytes());
5436 bytes[cursor + 15..cursor + 19].copy_from_slice(&(CHUNK_COLS as u32).to_le_bytes());
5437
5438 bytes[ROOT_OFFSET..ROOT_OFFSET + 4].copy_from_slice(b"TREE");
5439 bytes[ROOT_OFFSET + 4] = 1;
5440 bytes[ROOT_OFFSET + 5] = 2;
5441 bytes[ROOT_OFFSET + 6..ROOT_OFFSET + 8].copy_from_slice(&(2u16).to_le_bytes());
5442 bytes[ROOT_OFFSET + 8..ROOT_OFFSET + 16].copy_from_slice(&u64::MAX.to_le_bytes());
5443 bytes[ROOT_OFFSET + 16..ROOT_OFFSET + 24].copy_from_slice(&u64::MAX.to_le_bytes());
5444
5445 let mut node_cursor = ROOT_OFFSET + 24;
5446 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5447 node_cursor += 8;
5448 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5449 node_cursor += 8;
5450 bytes[node_cursor..node_cursor + 8]
5451 .copy_from_slice(&(FIRST_INTERNAL_OFFSET as u64).to_le_bytes());
5452 node_cursor += 8;
5453 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5454 node_cursor += 8;
5455 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(6u64).to_le_bytes());
5456 node_cursor += 8;
5457 bytes[node_cursor..node_cursor + 8]
5458 .copy_from_slice(&(SECOND_INTERNAL_OFFSET as u64).to_le_bytes());
5459
5460 bytes[FIRST_INTERNAL_OFFSET..FIRST_INTERNAL_OFFSET + 4].copy_from_slice(b"TREE");
5461 bytes[FIRST_INTERNAL_OFFSET + 4] = 1;
5462 bytes[FIRST_INTERNAL_OFFSET + 5] = 1;
5463 bytes[FIRST_INTERNAL_OFFSET + 6..FIRST_INTERNAL_OFFSET + 8]
5464 .copy_from_slice(&(2u16).to_le_bytes());
5465 bytes[FIRST_INTERNAL_OFFSET + 8..FIRST_INTERNAL_OFFSET + 16]
5466 .copy_from_slice(&u64::MAX.to_le_bytes());
5467 bytes[FIRST_INTERNAL_OFFSET + 16..FIRST_INTERNAL_OFFSET + 24]
5468 .copy_from_slice(&u64::MAX.to_le_bytes());
5469 let mut node_cursor = FIRST_INTERNAL_OFFSET + 24;
5470 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5471 node_cursor += 8;
5472 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5473 node_cursor += 8;
5474 bytes[node_cursor..node_cursor + 8]
5475 .copy_from_slice(&(FIRST_LEAF_OFFSET as u64).to_le_bytes());
5476 node_cursor += 8;
5477 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5478 node_cursor += 8;
5479 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(4u64).to_le_bytes());
5480 node_cursor += 8;
5481 bytes[node_cursor..node_cursor + 8]
5482 .copy_from_slice(&(SECOND_LEAF_OFFSET as u64).to_le_bytes());
5483
5484 bytes[SECOND_INTERNAL_OFFSET..SECOND_INTERNAL_OFFSET + 4].copy_from_slice(b"TREE");
5485 bytes[SECOND_INTERNAL_OFFSET + 4] = 1;
5486 bytes[SECOND_INTERNAL_OFFSET + 5] = 1;
5487 bytes[SECOND_INTERNAL_OFFSET + 6..SECOND_INTERNAL_OFFSET + 8]
5488 .copy_from_slice(&(1u16).to_le_bytes());
5489 bytes[SECOND_INTERNAL_OFFSET + 8..SECOND_INTERNAL_OFFSET + 16]
5490 .copy_from_slice(&u64::MAX.to_le_bytes());
5491 bytes[SECOND_INTERNAL_OFFSET + 16..SECOND_INTERNAL_OFFSET + 24]
5492 .copy_from_slice(&u64::MAX.to_le_bytes());
5493 let mut node_cursor = SECOND_INTERNAL_OFFSET + 24;
5494 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5495 node_cursor += 8;
5496 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(6u64).to_le_bytes());
5497 node_cursor += 8;
5498 bytes[node_cursor..node_cursor + 8]
5499 .copy_from_slice(&(THIRD_LEAF_OFFSET as u64).to_le_bytes());
5500
5501 let mut node_cursor = FIRST_LEAF_OFFSET;
5502 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
5503 bytes[node_cursor + 4] = 1;
5504 bytes[node_cursor + 5] = 0;
5505 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(1u16).to_le_bytes());
5506 bytes[node_cursor + 8..node_cursor + 16].copy_from_slice(&u64::MAX.to_le_bytes());
5507 bytes[node_cursor + 16..node_cursor + 24].copy_from_slice(&u64::MAX.to_le_bytes());
5508 node_cursor += 24;
5509 bytes[node_cursor..node_cursor + 8]
5510 .copy_from_slice(&(payload_a.len() as u64).to_le_bytes());
5511 node_cursor += 8;
5512 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5513 node_cursor += 8;
5514 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5515 node_cursor += 8;
5516 bytes[node_cursor..node_cursor + 8]
5517 .copy_from_slice(&(PAYLOAD_A_OFFSET as u64).to_le_bytes());
5518
5519 let mut node_cursor = SECOND_LEAF_OFFSET;
5520 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
5521 bytes[node_cursor + 4] = 1;
5522 bytes[node_cursor + 5] = 0;
5523 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(1u16).to_le_bytes());
5524 bytes[node_cursor + 8..node_cursor + 16].copy_from_slice(&u64::MAX.to_le_bytes());
5525 bytes[node_cursor + 16..node_cursor + 24].copy_from_slice(&u64::MAX.to_le_bytes());
5526 node_cursor += 24;
5527 bytes[node_cursor..node_cursor + 8]
5528 .copy_from_slice(&(payload_b.len() as u64).to_le_bytes());
5529 node_cursor += 8;
5530 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5531 node_cursor += 8;
5532 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5533 node_cursor += 8;
5534 bytes[node_cursor..node_cursor + 8]
5535 .copy_from_slice(&(PAYLOAD_B_OFFSET as u64).to_le_bytes());
5536
5537 let mut node_cursor = THIRD_LEAF_OFFSET;
5538 bytes[node_cursor..node_cursor + 4].copy_from_slice(b"TREE");
5539 bytes[node_cursor + 4] = 1;
5540 bytes[node_cursor + 5] = 0;
5541 bytes[node_cursor + 6..node_cursor + 8].copy_from_slice(&(1u16).to_le_bytes());
5542 bytes[node_cursor + 8..node_cursor + 16].copy_from_slice(&u64::MAX.to_le_bytes());
5543 bytes[node_cursor + 16..node_cursor + 24].copy_from_slice(&u64::MAX.to_le_bytes());
5544 node_cursor += 24;
5545 bytes[node_cursor..node_cursor + 8]
5546 .copy_from_slice(&(payload_c.len() as u64).to_le_bytes());
5547 node_cursor += 8;
5548 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5549 node_cursor += 8;
5550 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(4u64).to_le_bytes());
5551 node_cursor += 8;
5552 bytes[node_cursor..node_cursor + 8]
5553 .copy_from_slice(&(PAYLOAD_C_OFFSET as u64).to_le_bytes());
5554
5555 bytes[PAYLOAD_A_OFFSET..PAYLOAD_A_OFFSET + payload_a.len()].copy_from_slice(&payload_a);
5556 bytes[PAYLOAD_B_OFFSET..PAYLOAD_B_OFFSET + payload_b.len()].copy_from_slice(&payload_b);
5557 bytes[PAYLOAD_C_OFFSET..PAYLOAD_C_OFFSET + payload_c.len()].copy_from_slice(&payload_c);
5558
5559 file.write_all(&bytes)
5560 .expect("synthetic HDF5 multilevel-root fixture should be writable");
5561 }
5562
5563 fn write_synthetic_hdf5_chunked_malformed_multilevel_root_fixture(
5564 file: &mut tempfile::NamedTempFile,
5565 ) {
5566 const DATASET_PATH: &str = "/ScienceData/NDVI_Chunked_MalformedMultiLevel";
5567 const HEADER_OFFSET: usize = 1024;
5568 const ROOT_OFFSET: usize = 4096;
5569 const ROWS: usize = 2;
5570 const COLS: usize = 4;
5571 const CHUNK_ROWS: usize = 2;
5572 const CHUNK_COLS: usize = 2;
5573
5574 let total_len = ROOT_OFFSET + 64;
5575 let mut bytes = vec![0u8; total_len];
5576 let marker = DATASET_PATH.as_bytes();
5577 bytes[64..64 + marker.len()].copy_from_slice(marker);
5578
5579 let mut cursor = HEADER_OFFSET;
5580 bytes[cursor] = 1;
5581 bytes[cursor + 1] = 0;
5582 bytes[cursor + 2..cursor + 4].copy_from_slice(&(3u16).to_le_bytes());
5583 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5584 bytes[cursor + 8..cursor + 12].copy_from_slice(&(95u32).to_le_bytes());
5585 bytes[cursor + 12..cursor + 16].copy_from_slice(&0u32.to_le_bytes());
5586 cursor += 16;
5587
5588 bytes[cursor..cursor + 2].copy_from_slice(&(0x0001u16).to_le_bytes());
5589 bytes[cursor + 2..cursor + 4].copy_from_slice(&(24u16).to_le_bytes());
5590 bytes[cursor + 4] = 0;
5591 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5592 cursor += 8;
5593 bytes[cursor] = 2;
5594 bytes[cursor + 1] = 2;
5595 bytes[cursor + 2] = 0;
5596 bytes[cursor + 3] = 0;
5597 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5598 bytes[cursor + 8..cursor + 16].copy_from_slice(&(ROWS as u64).to_le_bytes());
5599 bytes[cursor + 16..cursor + 24].copy_from_slice(&(COLS as u64).to_le_bytes());
5600 cursor += 24;
5601
5602 bytes[cursor..cursor + 2].copy_from_slice(&(0x0003u16).to_le_bytes());
5603 bytes[cursor + 2..cursor + 4].copy_from_slice(&(8u16).to_le_bytes());
5604 bytes[cursor + 4] = 0;
5605 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5606 cursor += 8;
5607 bytes[cursor] = 0x13;
5608 bytes[cursor + 1] = 0;
5609 bytes[cursor + 2] = 0;
5610 bytes[cursor + 3] = 0;
5611 bytes[cursor + 4..cursor + 8].copy_from_slice(&(4u32).to_le_bytes());
5612 cursor += 8;
5613
5614 bytes[cursor..cursor + 2].copy_from_slice(&(0x0008u16).to_le_bytes());
5615 bytes[cursor + 2..cursor + 4].copy_from_slice(&(19u16).to_le_bytes());
5616 bytes[cursor + 4] = 0;
5617 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5618 cursor += 8;
5619 bytes[cursor] = 3;
5620 bytes[cursor + 1] = 2;
5621 bytes[cursor + 2] = 2;
5622 bytes[cursor + 3..cursor + 11].copy_from_slice(&(ROOT_OFFSET as u64).to_le_bytes());
5623 bytes[cursor + 11..cursor + 15].copy_from_slice(&(CHUNK_ROWS as u32).to_le_bytes());
5624 bytes[cursor + 15..cursor + 19].copy_from_slice(&(CHUNK_COLS as u32).to_le_bytes());
5625
5626 bytes[ROOT_OFFSET..ROOT_OFFSET + 4].copy_from_slice(b"TREE");
5627 bytes[ROOT_OFFSET + 4] = 1;
5628 bytes[ROOT_OFFSET + 5] = 2;
5629 bytes[ROOT_OFFSET + 6..ROOT_OFFSET + 8].copy_from_slice(&(1u16).to_le_bytes());
5630 bytes[ROOT_OFFSET + 8..ROOT_OFFSET + 16].copy_from_slice(&u64::MAX.to_le_bytes());
5631 bytes[ROOT_OFFSET + 16..ROOT_OFFSET + 24].copy_from_slice(&u64::MAX.to_le_bytes());
5632
5633 file.write_all(&bytes)
5634 .expect("synthetic malformed multilevel-root fixture should be writable");
5635 }
5636
5637 fn write_synthetic_hdf5_chunked_malformed_multilevel_fanout_fixture(
5638 file: &mut tempfile::NamedTempFile,
5639 ) {
5640 const DATASET_PATH: &str = "/ScienceData/NDVI_Chunked_MalformedMultiLevelFanout";
5641 const HEADER_OFFSET: usize = 1024;
5642 const ROOT_OFFSET: usize = 4096;
5643 const FIRST_INTERNAL_OFFSET: usize = 4168;
5644 const SECOND_INTERNAL_OFFSET: usize = 4256;
5645 const FIRST_LEAF_OFFSET: usize = 4344;
5646 const SECOND_LEAF_OFFSET: usize = 4432;
5647 const ROWS: usize = 2;
5648 const COLS: usize = 6;
5649 const CHUNK_ROWS: usize = 2;
5650 const CHUNK_COLS: usize = 2;
5651
5652 let total_len = SECOND_LEAF_OFFSET + 64;
5653 let mut bytes = vec![0u8; total_len];
5654 let marker = DATASET_PATH.as_bytes();
5655 bytes[64..64 + marker.len()].copy_from_slice(marker);
5656
5657 let mut cursor = HEADER_OFFSET;
5658 bytes[cursor] = 1;
5659 bytes[cursor + 1] = 0;
5660 bytes[cursor + 2..cursor + 4].copy_from_slice(&(3u16).to_le_bytes());
5661 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5662 bytes[cursor + 8..cursor + 12].copy_from_slice(&(95u32).to_le_bytes());
5663 bytes[cursor + 12..cursor + 16].copy_from_slice(&0u32.to_le_bytes());
5664 cursor += 16;
5665
5666 bytes[cursor..cursor + 2].copy_from_slice(&(0x0001u16).to_le_bytes());
5667 bytes[cursor + 2..cursor + 4].copy_from_slice(&(24u16).to_le_bytes());
5668 bytes[cursor + 4] = 0;
5669 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5670 cursor += 8;
5671 bytes[cursor] = 2;
5672 bytes[cursor + 1] = 2;
5673 bytes[cursor + 2] = 0;
5674 bytes[cursor + 3] = 0;
5675 bytes[cursor + 4..cursor + 8].copy_from_slice(&0u32.to_le_bytes());
5676 bytes[cursor + 8..cursor + 16].copy_from_slice(&(ROWS as u64).to_le_bytes());
5677 bytes[cursor + 16..cursor + 24].copy_from_slice(&(COLS as u64).to_le_bytes());
5678 cursor += 24;
5679
5680 bytes[cursor..cursor + 2].copy_from_slice(&(0x0003u16).to_le_bytes());
5681 bytes[cursor + 2..cursor + 4].copy_from_slice(&(8u16).to_le_bytes());
5682 bytes[cursor + 4] = 0;
5683 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5684 cursor += 8;
5685 bytes[cursor] = 0x13;
5686 bytes[cursor + 1] = 0;
5687 bytes[cursor + 2] = 0;
5688 bytes[cursor + 3] = 0;
5689 bytes[cursor + 4..cursor + 8].copy_from_slice(&(4u32).to_le_bytes());
5690 cursor += 8;
5691
5692 bytes[cursor..cursor + 2].copy_from_slice(&(0x0008u16).to_le_bytes());
5693 bytes[cursor + 2..cursor + 4].copy_from_slice(&(19u16).to_le_bytes());
5694 bytes[cursor + 4] = 0;
5695 bytes[cursor + 5..cursor + 8].copy_from_slice(&[0, 0, 0]);
5696 cursor += 8;
5697 bytes[cursor] = 3;
5698 bytes[cursor + 1] = 2;
5699 bytes[cursor + 2] = 2;
5700 bytes[cursor + 3..cursor + 11].copy_from_slice(&(ROOT_OFFSET as u64).to_le_bytes());
5701 bytes[cursor + 11..cursor + 15].copy_from_slice(&(CHUNK_ROWS as u32).to_le_bytes());
5702 bytes[cursor + 15..cursor + 19].copy_from_slice(&(CHUNK_COLS as u32).to_le_bytes());
5703
5704 bytes[ROOT_OFFSET..ROOT_OFFSET + 4].copy_from_slice(b"TREE");
5705 bytes[ROOT_OFFSET + 4] = 1;
5706 bytes[ROOT_OFFSET + 5] = 2;
5707 bytes[ROOT_OFFSET + 6..ROOT_OFFSET + 8].copy_from_slice(&(2u16).to_le_bytes());
5708 bytes[ROOT_OFFSET + 8..ROOT_OFFSET + 16].copy_from_slice(&u64::MAX.to_le_bytes());
5709 bytes[ROOT_OFFSET + 16..ROOT_OFFSET + 24].copy_from_slice(&u64::MAX.to_le_bytes());
5710 let mut node_cursor = ROOT_OFFSET + 24;
5711 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5712 node_cursor += 8;
5713 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5714 node_cursor += 8;
5715 bytes[node_cursor..node_cursor + 8]
5716 .copy_from_slice(&(FIRST_INTERNAL_OFFSET as u64).to_le_bytes());
5717 node_cursor += 8;
5718 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5719 node_cursor += 8;
5720 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(6u64).to_le_bytes());
5721 node_cursor += 8;
5722 bytes[node_cursor..node_cursor + 8]
5723 .copy_from_slice(&(SECOND_INTERNAL_OFFSET as u64).to_le_bytes());
5724
5725 bytes[FIRST_INTERNAL_OFFSET..FIRST_INTERNAL_OFFSET + 4].copy_from_slice(b"TREE");
5726 bytes[FIRST_INTERNAL_OFFSET + 4] = 1;
5727 bytes[FIRST_INTERNAL_OFFSET + 5] = 1;
5728 bytes[FIRST_INTERNAL_OFFSET + 6..FIRST_INTERNAL_OFFSET + 8]
5729 .copy_from_slice(&(2u16).to_le_bytes());
5730 bytes[FIRST_INTERNAL_OFFSET + 8..FIRST_INTERNAL_OFFSET + 16]
5731 .copy_from_slice(&u64::MAX.to_le_bytes());
5732 bytes[FIRST_INTERNAL_OFFSET + 16..FIRST_INTERNAL_OFFSET + 24]
5733 .copy_from_slice(&u64::MAX.to_le_bytes());
5734 let mut node_cursor = FIRST_INTERNAL_OFFSET + 24;
5735 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5736 node_cursor += 8;
5737 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5738 node_cursor += 8;
5739 bytes[node_cursor..node_cursor + 8]
5740 .copy_from_slice(&(FIRST_LEAF_OFFSET as u64).to_le_bytes());
5741 node_cursor += 8;
5742 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5743 node_cursor += 8;
5744 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(4u64).to_le_bytes());
5745 node_cursor += 8;
5746 bytes[node_cursor..node_cursor + 8]
5747 .copy_from_slice(&(SECOND_LEAF_OFFSET as u64).to_le_bytes());
5748
5749 bytes[FIRST_LEAF_OFFSET..FIRST_LEAF_OFFSET + 4].copy_from_slice(b"TREE");
5750 bytes[FIRST_LEAF_OFFSET + 4] = 1;
5751 bytes[FIRST_LEAF_OFFSET + 5] = 0;
5752 bytes[FIRST_LEAF_OFFSET + 6..FIRST_LEAF_OFFSET + 8].copy_from_slice(&(1u16).to_le_bytes());
5753 bytes[FIRST_LEAF_OFFSET + 8..FIRST_LEAF_OFFSET + 16]
5754 .copy_from_slice(&u64::MAX.to_le_bytes());
5755 bytes[FIRST_LEAF_OFFSET + 16..FIRST_LEAF_OFFSET + 24]
5756 .copy_from_slice(&u64::MAX.to_le_bytes());
5757 node_cursor = FIRST_LEAF_OFFSET + 24;
5758 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(16u64).to_le_bytes());
5759 node_cursor += 8;
5760 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5761 node_cursor += 8;
5762 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5763 node_cursor += 8;
5764 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(8192u64).to_le_bytes());
5765
5766 bytes[SECOND_LEAF_OFFSET..SECOND_LEAF_OFFSET + 4].copy_from_slice(b"TREE");
5767 bytes[SECOND_LEAF_OFFSET + 4] = 1;
5768 bytes[SECOND_LEAF_OFFSET + 5] = 0;
5769 bytes[SECOND_LEAF_OFFSET + 6..SECOND_LEAF_OFFSET + 8]
5770 .copy_from_slice(&(1u16).to_le_bytes());
5771 bytes[SECOND_LEAF_OFFSET + 8..SECOND_LEAF_OFFSET + 16]
5772 .copy_from_slice(&u64::MAX.to_le_bytes());
5773 bytes[SECOND_LEAF_OFFSET + 16..SECOND_LEAF_OFFSET + 24]
5774 .copy_from_slice(&u64::MAX.to_le_bytes());
5775 node_cursor = SECOND_LEAF_OFFSET + 24;
5776 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(16u64).to_le_bytes());
5777 node_cursor += 8;
5778 bytes[node_cursor..node_cursor + 8].copy_from_slice(&0u64.to_le_bytes());
5779 node_cursor += 8;
5780 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(2u64).to_le_bytes());
5781 node_cursor += 8;
5782 bytes[node_cursor..node_cursor + 8].copy_from_slice(&(8208u64).to_le_bytes());
5783
5784 file.write_all(&bytes)
5785 .expect("synthetic malformed multilevel-fanout fixture should be writable");
5786 }
5787
5788 fn encode_f32_values_as_zlib(values: &[f32]) -> Vec<u8> {
5789 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
5790 for value in values {
5791 encoder
5792 .write_all(&value.to_le_bytes())
5793 .expect("zlib encoder should accept f32 bytes");
5794 }
5795 encoder
5796 .finish()
5797 .expect("zlib encoder should finish synthetic payload")
5798 }
5799
5800 #[test]
5801 fn raster_read_hdf4_dataset_uri_reads_supported_layout() {
5802 let mut tmp = tempfile::Builder::new()
5803 .suffix(".hdf")
5804 .tempfile()
5805 .expect("temp file should be created");
5806 write_synthetic_hdf4_i16_fixture(&mut tmp);
5807
5808 let uri = format!("{}:///GridA/FieldA", tmp.path().to_string_lossy());
5809 let raster = Raster::read(&uri).expect("HDF4 dataset URI should decode to raster");
5810
5811 assert_eq!(raster.rows, 2);
5812 assert_eq!(raster.cols, 4);
5813 assert_eq!(raster.bands, 1);
5814 assert_eq!(raster.data_type, DataType::I16);
5815 assert_eq!(raster.get(0, 0, 0), 1.0);
5816 assert_eq!(raster.get(0, 1, 3), 8.0);
5817 }
5818
5819 #[test]
5820 fn raster_read_hdf4_dataset_uri_reads_supported_layout_with_canonical_selector() {
5821 let mut tmp = tempfile::Builder::new()
5822 .suffix(".hdf")
5823 .tempfile()
5824 .expect("temp file should be created");
5825 write_synthetic_hdf4_i16_fixture(&mut tmp);
5826
5827 let uri = format!("{}#dataset=/GridA/FieldA", tmp.path().to_string_lossy());
5828 let raster =
5829 Raster::read(&uri).expect("canonical HDF4 dataset URI should decode to raster");
5830
5831 assert_eq!(raster.rows, 2);
5832 assert_eq!(raster.cols, 4);
5833 assert_eq!(raster.get(0, 0, 0), 1.0);
5834 assert_eq!(raster.get(0, 1, 3), 8.0);
5835 }
5836
5837 #[test]
5838 fn raster_read_hdf_dataset_uri_reports_missing_dataset_path() {
5839 let mut tmp = tempfile::Builder::new()
5840 .suffix(".hdf")
5841 .tempfile()
5842 .expect("temp file should be created");
5843 write_synthetic_hdf4_i16_fixture(&mut tmp);
5844
5845 let uri = format!("{}:///GridA/DoesNotExist", tmp.path().to_string_lossy());
5846 let err = Raster::read(&uri).expect_err("missing dataset path should fail");
5847 assert!(err.to_string().contains("dataset path resolution failed"));
5848 }
5849
5850 #[test]
5851 fn raster_read_hdf5_dataset_uri_reads_supported_gedi_path() {
5852 let mut tmp = tempfile::Builder::new()
5853 .suffix(".h5")
5854 .tempfile()
5855 .expect("temp file should be created");
5856 write_synthetic_hdf5_gedi_contiguous_fixture(&mut tmp);
5857
5858 let uri = format!(
5859 "{}:///BEAM0000/elev_lowestmode",
5860 tmp.path().to_string_lossy()
5861 );
5862 let raster = Raster::read(&uri).expect("HDF5 GEDI URI should materialize to raster");
5863
5864 assert_eq!(raster.rows, 1);
5865 assert_eq!(raster.cols, 89_634);
5866 assert_eq!(raster.bands, 1);
5867 assert_eq!(raster.data_type, DataType::F32);
5868 assert_eq!(raster.get(0, 0, 0), 0.0);
5869 assert_eq!(raster.get(0, 0, 12_345), 12_345.0);
5870 assert_eq!(raster.get(0, 0, 89_633), 89_633.0);
5871 }
5872
5873 #[test]
5874 fn raster_read_hdf5_dataset_uri_reads_supported_gedi_path_with_canonical_selector() {
5875 let mut tmp = tempfile::Builder::new()
5876 .suffix(".h5")
5877 .tempfile()
5878 .expect("temp file should be created");
5879 write_synthetic_hdf5_gedi_contiguous_fixture(&mut tmp);
5880
5881 let uri = format!(
5882 "{}#dataset=/BEAM0000/elev_lowestmode",
5883 tmp.path().to_string_lossy()
5884 );
5885 let raster =
5886 Raster::read(&uri).expect("canonical HDF5 GEDI URI should materialize to raster");
5887
5888 assert_eq!(raster.rows, 1);
5889 assert_eq!(raster.cols, 89_634);
5890 assert_eq!(raster.get(0, 0, 0), 0.0);
5891 assert_eq!(raster.get(0, 0, 12_345), 12_345.0);
5892 }
5893
5894 #[test]
5895 fn raster_read_hdf5_dataset_uri_reads_supported_viirs_xdim_path() {
5896 let mut tmp = tempfile::Builder::new()
5897 .suffix(".h5")
5898 .tempfile()
5899 .expect("temp file should be created");
5900 write_synthetic_hdf5_viirs_xdim_fixture(&mut tmp);
5901
5902 let uri = format!(
5903 "{}:///HDFEOS/GRIDS/VIIRS_Grid_8Day_VI_500m/XDim",
5904 tmp.path().to_string_lossy()
5905 );
5906 let raster = Raster::read(&uri).expect("HDF5 VIIRS URI should materialize to raster");
5907
5908 assert_eq!(raster.rows, 1);
5909 assert_eq!(raster.cols, 2_400);
5910 assert_eq!(raster.bands, 1);
5911 assert_eq!(raster.data_type, DataType::F64);
5912 assert_eq!(raster.get(0, 0, 0), 0.5);
5913 assert_eq!(raster.get(0, 0, 1_234), 1_234.5);
5914 assert_eq!(raster.get(0, 0, 2_399), 2_399.5);
5915 }
5916
5917 #[test]
5918 fn raster_read_hdf5_dataset_uri_reads_generic_contiguous_f32_path() {
5919 let mut tmp = tempfile::Builder::new()
5920 .suffix(".h5")
5921 .tempfile()
5922 .expect("temp file should be created");
5923 write_synthetic_hdf5_generic_contiguous_f32_fixture(&mut tmp);
5924
5925 let uri = format!("{}#dataset=/ScienceData/NDVI", tmp.path().to_string_lossy());
5926 let raster = Raster::read(&uri).expect("generic contiguous HDF5 URI should materialize");
5927
5928 assert_eq!(raster.rows, 1);
5929 assert_eq!(raster.cols, 12);
5930 assert_eq!(raster.bands, 1);
5931 assert_eq!(raster.data_type, DataType::F32);
5932 assert_eq!(raster.get(0, 0, 0), 0.0);
5933 assert_eq!(raster.get(0, 0, 4), 1.0);
5934 assert_eq!(raster.get(0, 0, 11), 2.75);
5935 }
5936
5937 #[test]
5938 fn raster_read_hdf5_dataset_uri_reads_chunked_single_chunk_f32_path() {
5939 let mut tmp = tempfile::Builder::new()
5940 .suffix(".h5")
5941 .tempfile()
5942 .expect("temp file should be created");
5943 write_synthetic_hdf5_chunked_single_chunk_f32_fixture(&mut tmp);
5944
5945 let uri = format!(
5946 "{}#dataset=/ScienceData/NDVI_Chunked",
5947 tmp.path().to_string_lossy()
5948 );
5949 let raster = Raster::read(&uri).expect("chunked single-chunk HDF5 URI should materialize");
5950
5951 assert_eq!(raster.rows, 2);
5952 assert_eq!(raster.cols, 3);
5953 assert_eq!(raster.bands, 1);
5954 assert_eq!(raster.data_type, DataType::F32);
5955 assert_eq!(raster.get(0, 0, 0), 0.5);
5956 assert_eq!(raster.get(0, 0, 2), 2.5);
5957 assert_eq!(raster.get(0, 1, 2), 5.5);
5958 }
5959
5960 #[test]
5961 fn raster_read_hdf5_dataset_uri_reads_chunked_two_chunk_f32_path() {
5962 let mut tmp = tempfile::Builder::new()
5963 .suffix(".h5")
5964 .tempfile()
5965 .expect("temp file should be created");
5966 write_synthetic_hdf5_chunked_two_chunk_f32_fixture(&mut tmp);
5967
5968 let uri = format!(
5969 "{}#dataset=/ScienceData/NDVI_Chunked_Two",
5970 tmp.path().to_string_lossy()
5971 );
5972 let raster = Raster::read(&uri).expect("chunked two-chunk HDF5 URI should materialize");
5973
5974 assert_eq!(raster.rows, 2);
5975 assert_eq!(raster.cols, 4);
5976 assert_eq!(raster.bands, 1);
5977 assert_eq!(raster.data_type, DataType::F32);
5978 assert_eq!(raster.get(0, 0, 0), 0.5);
5979 assert_eq!(raster.get(0, 0, 3), 3.5);
5980 assert_eq!(raster.get(0, 1, 0), 4.5);
5981 assert_eq!(raster.get(0, 1, 3), 7.5);
5982 }
5983
5984 #[test]
5985 fn raster_read_hdf5_dataset_uri_reads_chunked_two_chunk_f32_deflate_path() {
5986 let mut tmp = tempfile::Builder::new()
5987 .suffix(".h5")
5988 .tempfile()
5989 .expect("temp file should be created");
5990 write_synthetic_hdf5_chunked_two_chunk_f32_deflate_fixture(&mut tmp);
5991
5992 let uri = format!(
5993 "{}#dataset=/ScienceData/NDVI_Chunked_Two_Deflate",
5994 tmp.path().to_string_lossy()
5995 );
5996 let raster =
5997 Raster::read(&uri).expect("chunked deflate two-chunk HDF5 URI should materialize");
5998
5999 assert_eq!(raster.rows, 2);
6000 assert_eq!(raster.cols, 4);
6001 assert_eq!(raster.bands, 1);
6002 assert_eq!(raster.data_type, DataType::F32);
6003 assert_eq!(raster.get(0, 0, 0), 0.5);
6004 assert_eq!(raster.get(0, 0, 3), 3.5);
6005 assert_eq!(raster.get(0, 1, 0), 4.5);
6006 assert_eq!(raster.get(0, 1, 3), 7.5);
6007 }
6008
6009 #[test]
6010 fn raster_read_hdf5_dataset_uri_reads_chunked_two_leaf_f32_path() {
6011 let mut tmp = tempfile::Builder::new()
6012 .suffix(".h5")
6013 .tempfile()
6014 .expect("temp file should be created");
6015 write_synthetic_hdf5_chunked_two_leaf_f32_fixture(&mut tmp);
6016
6017 let uri = format!(
6018 "{}#dataset=/ScienceData/NDVI_Chunked_Two_Leaf",
6019 tmp.path().to_string_lossy()
6020 );
6021 let raster = Raster::read(&uri).expect("chunked two-leaf HDF5 URI should materialize");
6022
6023 assert_eq!(raster.rows, 2);
6024 assert_eq!(raster.cols, 4);
6025 assert_eq!(raster.bands, 1);
6026 assert_eq!(raster.data_type, DataType::F32);
6027 assert_eq!(raster.get(0, 0, 0), 0.5);
6028 assert_eq!(raster.get(0, 0, 3), 3.5);
6029 assert_eq!(raster.get(0, 1, 0), 4.5);
6030 assert_eq!(raster.get(0, 1, 3), 7.5);
6031 }
6032
6033 #[test]
6034 fn raster_read_hdf5_dataset_uri_reads_chunked_internal_root_f32_path() {
6035 let mut tmp = tempfile::Builder::new()
6036 .suffix(".h5")
6037 .tempfile()
6038 .expect("temp file should be created");
6039 write_synthetic_hdf5_chunked_internal_root_fixture(&mut tmp);
6040
6041 let uri = format!(
6042 "{}#dataset=/ScienceData/NDVI_Chunked_InternalRoot",
6043 tmp.path().to_string_lossy()
6044 );
6045 let raster = Raster::read(&uri).expect("internal-root chunk index should materialize");
6046 assert_eq!(raster.rows, 2);
6047 assert_eq!(raster.cols, 4);
6048 assert_eq!(raster.bands, 1);
6049 assert_eq!(raster.data_type, DataType::F32);
6050 assert_eq!(raster.get(0, 0, 0), 0.5);
6051 assert_eq!(raster.get(0, 0, 3), 3.5);
6052 assert_eq!(raster.get(0, 1, 0), 4.5);
6053 assert_eq!(raster.get(0, 1, 3), 7.5);
6054 }
6055
6056 #[test]
6057 fn raster_read_hdf5_dataset_uri_reads_chunked_multilevel_root_f32_path() {
6058 let mut tmp = tempfile::Builder::new()
6059 .suffix(".h5")
6060 .tempfile()
6061 .expect("temp file should be created");
6062 write_synthetic_hdf5_chunked_multilevel_root_fixture(&mut tmp);
6063
6064 let uri = format!(
6065 "{}#dataset=/ScienceData/NDVI_Chunked_MultiLevelRoot",
6066 tmp.path().to_string_lossy()
6067 );
6068 let raster = Raster::read(&uri).expect("multilevel root chunk index should materialize");
6069 assert_eq!(raster.rows, 2);
6070 assert_eq!(raster.cols, 6);
6071 assert_eq!(raster.bands, 1);
6072 assert_eq!(raster.data_type, DataType::F32);
6073 assert_eq!(raster.get(0, 0, 0), 0.5);
6074 assert_eq!(raster.get(0, 0, 3), 3.5);
6075 assert_eq!(raster.get(0, 0, 5), 9.5);
6076 assert_eq!(raster.get(0, 1, 0), 4.5);
6077 assert_eq!(raster.get(0, 1, 3), 7.5);
6078 assert_eq!(raster.get(0, 1, 5), 11.5);
6079 }
6080
6081 #[test]
6082 fn raster_read_hdf5_dataset_uri_reports_malformed_multilevel_root_as_unsupported() {
6083 let mut tmp = tempfile::Builder::new()
6084 .suffix(".h5")
6085 .tempfile()
6086 .expect("temp file should be created");
6087 write_synthetic_hdf5_chunked_malformed_multilevel_root_fixture(&mut tmp);
6088
6089 let uri = format!(
6090 "{}#dataset=/ScienceData/NDVI_Chunked_MalformedMultiLevel",
6091 tmp.path().to_string_lossy()
6092 );
6093 let err = Raster::read(&uri).expect_err("malformed multilevel root should fail explicitly");
6094 let msg = err.to_string();
6095 assert!(
6096 msg.contains("B-tree node is missing TREE signature")
6097 || msg.contains("exhausted internal-level budget")
6098 || msg.contains("internal-node cycle")
6099 || msg.contains("invalid child address"),
6100 "unexpected malformed-root diagnostic: {msg}"
6101 );
6102 }
6103
6104 #[test]
6105 fn raster_read_hdf5_dataset_uri_reports_malformed_multilevel_fanout_as_unsupported() {
6106 let mut tmp = tempfile::Builder::new()
6107 .suffix(".h5")
6108 .tempfile()
6109 .expect("temp file should be created");
6110 write_synthetic_hdf5_chunked_malformed_multilevel_fanout_fixture(&mut tmp);
6111
6112 let uri = format!(
6113 "{}#dataset=/ScienceData/NDVI_Chunked_MalformedMultiLevelFanout",
6114 tmp.path().to_string_lossy()
6115 );
6116 let err =
6117 Raster::read(&uri).expect_err("malformed multilevel fanout should fail explicitly");
6118 let msg = err.to_string();
6119 assert!(
6120 msg.contains("B-tree node is missing TREE signature")
6121 || msg.contains("exhausted internal-level budget")
6122 || msg.contains("internal-node cycle")
6123 || msg.contains("invalid child address"),
6124 "unexpected malformed-fanout diagnostic: {msg}"
6125 );
6126 }
6127
6128 #[test]
6129 fn raster_read_hdf5_dataset_uri_reports_unimplemented_materialization() {
6130 let err = Raster::read("mock_scene.h5:///ScienceData/DoesNotExist")
6131 .expect_err("HDF5 dataset URI should currently fail explicitly");
6132 assert!(err
6133 .to_string()
6134 .contains("HDF5 dataset path resolution failed"));
6135 }
6136
6137 #[test]
6138 fn get_set() {
6139 let mut r = make_raster();
6140 assert_eq!(r.get(0, 0, 0), 0.0);
6141 assert_eq!(r.get(0, 2, 3), 11.0);
6142 r.set(0, 1, 1, -9999.0).unwrap();
6143 assert!(r.is_nodata(r.get(0, 1, 1))); assert_eq!(r.get_opt(0, 1, 1), None); }
6146
6147 #[test]
6148 fn statistics() {
6149 let r = make_raster();
6150 let s = r.statistics();
6151 assert_eq!(s.valid_count, 12);
6152 assert_eq!(s.min, 0.0);
6153 assert_eq!(s.max, 11.0);
6154 assert!((s.mean - 5.5).abs() < 1e-10);
6155 }
6156
6157 #[test]
6158 fn world_to_pixel() {
6159 let r = make_raster();
6160 assert_eq!(r.world_to_pixel(5.0, 25.0), Some((0, 0)));
6162 assert_eq!(r.world_to_pixel(35.0, 5.0), Some((3, 2)));
6163 assert_eq!(r.world_to_pixel(-1.0, 0.0), None);
6164 }
6165
6166 #[test]
6167 fn extent() {
6168 let r = make_raster();
6169 let e = r.extent();
6170 assert_eq!(e.x_min, 0.0);
6171 assert_eq!(e.y_min, 0.0);
6172 assert_eq!(e.x_max, 40.0);
6173 assert_eq!(e.y_max, 30.0);
6174 }
6175
6176 #[test]
6177 fn sample_extent_boundary_points_count_and_corners() {
6178 let e = Extent {
6179 x_min: 0.0,
6180 y_min: 0.0,
6181 x_max: 10.0,
6182 y_max: 5.0,
6183 };
6184 let pts = sample_extent_boundary_points(e, 8);
6185 assert_eq!(pts.len(), 32);
6186 assert!(pts.contains(&(0.0, 0.0)));
6187 assert!(pts.contains(&(0.0, 5.0)));
6188 assert!(pts.contains(&(10.0, 0.0)));
6189 assert!(pts.contains(&(10.0, 5.0)));
6190 }
6191
6192 #[test]
6193 fn sample_extent_boundary_points_minimum_sampling_when_zero_requested() {
6194 let e = Extent {
6195 x_min: -1.0,
6196 y_min: -2.0,
6197 x_max: 3.0,
6198 y_max: 4.0,
6199 };
6200 let pts = sample_extent_boundary_points(e, 0);
6201 assert_eq!(pts.len(), 4);
6202 assert!(pts.contains(&(-1.0, -2.0)));
6203 assert!(pts.contains(&(-1.0, 4.0)));
6204 assert!(pts.contains(&(3.0, -2.0)));
6205 assert!(pts.contains(&(3.0, 4.0)));
6206 }
6207
6208 #[test]
6209 fn minimal_wrapped_longitude_bounds_handles_antimeridian_cluster() {
6210 let lons = [179.0, -179.0, 178.0, -178.0];
6211 let (x0, x1) = minimal_wrapped_longitude_bounds(&lons).unwrap();
6212 assert!((x1 - x0) < 6.0);
6213 }
6214
6215 #[test]
6216 fn antimeridian_aware_longitude_bounds_prefers_wrapped_interval() {
6217 let lons = [179.5, -179.5, 179.0, -179.0];
6218 let (x0, x1) =
6219 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Auto).unwrap();
6220 assert!((x1 - x0) < 5.0);
6221 assert!(x0 >= 0.0);
6222 }
6223
6224 #[test]
6225 fn antimeridian_aware_longitude_bounds_keeps_linear_when_better() {
6226 let lons = [-20.0, -10.0, 0.0, 10.0];
6227 let (x0, x1) =
6228 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Auto).unwrap();
6229 assert!((x0 - (-20.0)).abs() < 1e-12);
6230 assert!((x1 - 10.0).abs() < 1e-12);
6231 }
6232
6233 #[test]
6234 fn antimeridian_policy_controls_wrapped_vs_linear_behavior() {
6235 let lons = [179.5, -179.5, 179.0, -179.0];
6236
6237 let (ax0, ax1) =
6238 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Auto).unwrap();
6239 let (lx0, lx1) =
6240 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Linear).unwrap();
6241 let (wx0, wx1) =
6242 antimeridian_aware_longitude_bounds(&lons, AntimeridianPolicy::Wrap).unwrap();
6243
6244 assert!((ax1 - ax0) < (lx1 - lx0));
6245 assert!((wx1 - wx0) < (lx1 - lx0));
6246 assert!((wx1 - wx0 - (ax1 - ax0)).abs() < 1e-9);
6247 }
6248
6249 #[test]
6250 fn reproject_antimeridian_policy_linear_vs_wrap_changes_default_extent() {
6251 let cfg = RasterConfig {
6252 cols: 8,
6253 rows: 4,
6254 x_min: 170.0,
6255 y_min: -10.0,
6256 cell_size: 2.5,
6257 nodata: -9999.0,
6258 ..Default::default()
6259 };
6260 let mut r = Raster::new(cfg);
6261 r.crs = CrsInfo::from_epsg(4326);
6262 for row in 0..r.rows {
6263 for col in 0..r.cols {
6264 r.set(0, row as isize, col as isize, (row * r.cols + col) as f64)
6265 .unwrap();
6266 }
6267 }
6268
6269 let linear_opts = ReprojectOptions::new(4326, ResampleMethod::Nearest)
6270 .with_antimeridian_policy(AntimeridianPolicy::Linear);
6271 let wrap_opts = ReprojectOptions::new(4326, ResampleMethod::Nearest)
6272 .with_antimeridian_policy(AntimeridianPolicy::Wrap);
6273
6274 let linear = r.reproject_with_options(&linear_opts).unwrap();
6275 let wrap = r.reproject_with_options(&wrap_opts).unwrap();
6276
6277 let linear_width = linear.x_max() - linear.x_min;
6278 let wrap_width = wrap.x_max() - wrap.x_min;
6279
6280 assert!(linear_width.is_finite() && linear_width > 0.0);
6281 assert!(wrap_width.is_finite() && wrap_width > 0.0);
6282 assert!(wrap_width <= linear_width + 1e-9);
6283 }
6284
6285 #[test]
6286 fn reproject_antimeridian_policy_auto_matches_narrower_interval() {
6287 let cfg = RasterConfig {
6288 cols: 8,
6289 rows: 4,
6290 x_min: 170.0,
6291 y_min: -10.0,
6292 cell_size: 2.5,
6293 nodata: -9999.0,
6294 ..Default::default()
6295 };
6296 let mut r = Raster::new(cfg);
6297 r.crs = CrsInfo::from_epsg(4326);
6298 for row in 0..r.rows {
6299 for col in 0..r.cols {
6300 r.set(0, row as isize, col as isize, (row * r.cols + col) as f64)
6301 .unwrap();
6302 }
6303 }
6304
6305 let linear = r
6306 .reproject_with_options(
6307 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
6308 .with_antimeridian_policy(AntimeridianPolicy::Linear),
6309 )
6310 .unwrap();
6311 let wrap = r
6312 .reproject_with_options(
6313 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
6314 .with_antimeridian_policy(AntimeridianPolicy::Wrap),
6315 )
6316 .unwrap();
6317 let auto = r
6318 .reproject_with_options(
6319 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
6320 .with_antimeridian_policy(AntimeridianPolicy::Auto),
6321 )
6322 .unwrap();
6323
6324 let linear_width = linear.x_max() - linear.x_min;
6325 let wrap_width = wrap.x_max() - wrap.x_min;
6326 let auto_width = auto.x_max() - auto.x_min;
6327
6328 let expected = linear_width.min(wrap_width);
6329 assert!((auto_width - expected).abs() < 1e-9);
6330 }
6331
6332 #[test]
6333 fn reproject_to_epsg_requires_source_epsg() {
6334 let r = make_raster();
6335 let err = r.reproject_to_epsg_nearest(3857).unwrap_err();
6336 assert!(err
6337 .to_string()
6338 .contains("requires source CRS EPSG in raster.crs.epsg"));
6339 }
6340
6341 #[test]
6342 fn reproject_with_crs_allows_missing_source_epsg() {
6343 let cfg = RasterConfig {
6344 cols: 4,
6345 rows: 4,
6346 x_min: -1.0,
6347 y_min: -1.0,
6348 cell_size: 0.5,
6349 nodata: -9999.0,
6350 ..Default::default()
6351 };
6352 let mut r = Raster::new(cfg);
6353 for row in 0..4 {
6355 for col in 0..4 {
6356 r.set(0, row, col, (row * 4 + col) as f64).unwrap();
6357 }
6358 }
6359
6360 let src = Crs::from_epsg(4326).unwrap();
6361 let dst = Crs::from_epsg(3857).unwrap();
6362 let opts = ReprojectOptions::new(3857, ResampleMethod::Nearest);
6363
6364 let out = r.reproject_with_crs(&src, &dst, &opts).unwrap();
6365 assert_eq!(out.cols, 4);
6366 assert_eq!(out.rows, 4);
6367 assert_eq!(out.crs.epsg, Some(3857));
6368 assert!(out.statistics().valid_count > 0);
6369 }
6370
6371 #[test]
6372 fn reproject_with_options_and_progress_emits_live_updates() {
6373 let cfg = RasterConfig {
6374 cols: 6,
6375 rows: 5,
6376 x_min: -80.0,
6377 y_min: 40.0,
6378 cell_size: 0.1,
6379 nodata: -9999.0,
6380 ..Default::default()
6381 };
6382 let mut r = Raster::new(cfg);
6383 r.crs = CrsInfo::from_epsg(4326);
6384 for row in 0..r.rows {
6385 for col in 0..r.cols {
6386 r.set(0, row as isize, col as isize, (row * r.cols + col) as f64)
6387 .unwrap();
6388 }
6389 }
6390
6391 let progress_values: Arc<Mutex<Vec<f64>>> = Arc::new(Mutex::new(Vec::new()));
6392 let sink = Arc::clone(&progress_values);
6393
6394 let out = r
6395 .reproject_with_options_and_progress(
6396 &ReprojectOptions::new(3857, ResampleMethod::Nearest),
6397 move |pct| {
6398 sink.lock().unwrap().push(pct);
6399 },
6400 )
6401 .unwrap();
6402
6403 let values = progress_values.lock().unwrap();
6404 assert!(!values.is_empty());
6405 assert_eq!(values.len(), out.rows + 1);
6406 assert!(values
6407 .iter()
6408 .all(|v| v.is_finite() && *v >= 0.0 && *v <= 1.0));
6409 assert!((values.last().copied().unwrap() - 1.0).abs() < 1e-12);
6410 }
6411
6412 #[test]
6413 fn reproject_to_epsg_identity_preserves_data() {
6414 let mut r = make_raster();
6415 r.crs = CrsInfo::from_epsg(4326);
6416
6417 let r2 = r.reproject_to_epsg(4326, ResampleMethod::Nearest).unwrap();
6418 assert_eq!(r2.cols, r.cols);
6419 assert_eq!(r2.rows, r.rows);
6420 assert_eq!(r2.bands, r.bands);
6421 assert_eq!(r2.crs.epsg, Some(4326));
6422 assert_eq!(r2.get(0, 0, 0), r.get(0, 0, 0));
6423 assert_eq!(r2.get(0, 2, 3), r.get(0, 2, 3));
6424 }
6425
6426 #[test]
6427 fn reproject_to_epsg_4326_to_3857_produces_valid_output() {
6428 let cfg = RasterConfig {
6429 cols: 4,
6430 rows: 4,
6431 x_min: -1.0,
6432 y_min: -1.0,
6433 cell_size: 0.5,
6434 nodata: -9999.0,
6435 ..Default::default()
6436 };
6437 let mut r = Raster::new(cfg);
6438 r.crs = CrsInfo::from_epsg(4326);
6439 for row in 0..4 {
6440 for col in 0..4 {
6441 r.set(0, row, col, (row * 4 + col) as f64).unwrap();
6442 }
6443 }
6444
6445 let out = r.reproject_to_epsg_nearest(3857).unwrap();
6446 assert_eq!(out.cols, 4);
6447 assert_eq!(out.rows, 4);
6448 assert_eq!(out.crs.epsg, Some(3857));
6449 assert!(out.x_min.is_finite());
6450 assert!(out.y_min.is_finite());
6451 assert!(out.cell_size_x.is_finite() && out.cell_size_x > 0.0);
6452 assert!(out.cell_size_y.is_finite() && out.cell_size_y > 0.0);
6453 let s = out.statistics();
6454 assert!(s.valid_count > 0);
6455 }
6456
6457 #[test]
6458 fn reproject_to_match_grid_honors_target_grid_definition() {
6459 let src_cfg = RasterConfig {
6460 cols: 6,
6461 rows: 4,
6462 x_min: -3.0,
6463 y_min: -2.0,
6464 cell_size: 1.0,
6465 nodata: -9999.0,
6466 ..Default::default()
6467 };
6468 let mut src = Raster::new(src_cfg);
6469 src.crs = CrsInfo::from_epsg(4326);
6470 for row in 0..src.rows {
6471 for col in 0..src.cols {
6472 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
6473 .unwrap();
6474 }
6475 }
6476
6477 let target_cfg = RasterConfig {
6478 cols: 12,
6479 rows: 10,
6480 x_min: -500_000.0,
6481 y_min: -400_000.0,
6482 cell_size: 1.0,
6483 cell_size_y: Some(1.0),
6484 nodata: -9999.0,
6485 ..Default::default()
6486 };
6487 let mut target = Raster::new(target_cfg);
6488 target.crs = CrsInfo::from_epsg(3857);
6489 target.cell_size_x = (500_000.0 - (-500_000.0)) / target.cols as f64;
6490 target.cell_size_y = (400_000.0 - (-400_000.0)) / target.rows as f64;
6491
6492 let out = src
6493 .reproject_to_match_grid(&target, ResampleMethod::Bilinear)
6494 .unwrap();
6495
6496 assert_eq!(out.cols, target.cols);
6497 assert_eq!(out.rows, target.rows);
6498 assert_eq!(out.crs.epsg, target.crs.epsg);
6499 assert!((out.x_min - target.x_min).abs() < 1e-9);
6500 assert!((out.y_min - target.y_min).abs() < 1e-9);
6501 assert!((out.x_max() - target.x_max()).abs() < 1e-6);
6502 assert!((out.y_max() - target.y_max()).abs() < 1e-6);
6503 assert!(out.cell_size_x.is_finite() && out.cell_size_x > 0.0);
6504 assert!(out.cell_size_y.is_finite() && out.cell_size_y > 0.0);
6505 }
6506
6507 #[test]
6508 fn reproject_to_match_resolution_honors_reference_cellsize_and_snap() {
6509 let src_cfg = RasterConfig {
6510 cols: 6,
6511 rows: 4,
6512 x_min: -3.0,
6513 y_min: -2.0,
6514 cell_size: 1.0,
6515 nodata: -9999.0,
6516 ..Default::default()
6517 };
6518 let mut src = Raster::new(src_cfg);
6519 src.crs = CrsInfo::from_epsg(4326);
6520 for row in 0..src.rows {
6521 for col in 0..src.cols {
6522 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
6523 .unwrap();
6524 }
6525 }
6526
6527 let mut reference = Raster::new(RasterConfig {
6528 cols: 4,
6529 rows: 3,
6530 x_min: 50_000.0,
6531 y_min: -75_000.0,
6532 cell_size: 100_000.0,
6533 cell_size_y: Some(80_000.0),
6534 nodata: -9999.0,
6535 ..Default::default()
6536 });
6537 reference.crs = CrsInfo::from_epsg(3857);
6538
6539 let out = src
6540 .reproject_to_match_resolution(&reference, ResampleMethod::Nearest)
6541 .unwrap();
6542
6543 assert_eq!(out.crs.epsg, Some(3857));
6544 assert!((out.cell_size_x - reference.cell_size_x).abs() < 1e-9);
6545 assert!((out.cell_size_y - reference.cell_size_y).abs() < 1e-9);
6546
6547 let kx = ((out.x_min - reference.x_min) / reference.cell_size_x).round();
6548 let ky = ((out.y_min - reference.y_min) / reference.cell_size_y).round();
6549 assert!((out.x_min - (reference.x_min + kx * reference.cell_size_x)).abs() < 1e-6);
6550 assert!((out.y_min - (reference.y_min + ky * reference.cell_size_y)).abs() < 1e-6);
6551 assert!(out.cols > 0);
6552 assert!(out.rows > 0);
6553 }
6554
6555 #[test]
6556 fn reproject_to_match_resolution_in_epsg_same_crs_matches_reference_settings() {
6557 let src_cfg = RasterConfig {
6558 cols: 6,
6559 rows: 4,
6560 x_min: -3.0,
6561 y_min: -2.0,
6562 cell_size: 1.0,
6563 nodata: -9999.0,
6564 ..Default::default()
6565 };
6566 let mut src = Raster::new(src_cfg);
6567 src.crs = CrsInfo::from_epsg(4326);
6568 for row in 0..src.rows {
6569 for col in 0..src.cols {
6570 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
6571 .unwrap();
6572 }
6573 }
6574
6575 let mut reference = Raster::new(RasterConfig {
6576 cols: 4,
6577 rows: 3,
6578 x_min: -10.0,
6579 y_min: -20.0,
6580 cell_size: 0.5,
6581 cell_size_y: Some(0.25),
6582 nodata: -9999.0,
6583 ..Default::default()
6584 });
6585 reference.crs = CrsInfo::from_epsg(4326);
6586
6587 let out = src
6588 .reproject_to_match_resolution_in_epsg(4326, &reference, ResampleMethod::Nearest)
6589 .unwrap();
6590
6591 assert_eq!(out.crs.epsg, Some(4326));
6592 assert!((out.cell_size_x - reference.cell_size_x).abs() < 1e-12);
6593 assert!((out.cell_size_y - reference.cell_size_y).abs() < 1e-12);
6594 }
6595
6596 #[test]
6597 fn reproject_to_match_resolution_in_epsg_cross_crs_converts_resolution() {
6598 let src_cfg = RasterConfig {
6599 cols: 6,
6600 rows: 4,
6601 x_min: -3.0,
6602 y_min: -2.0,
6603 cell_size: 1.0,
6604 nodata: -9999.0,
6605 ..Default::default()
6606 };
6607 let mut src = Raster::new(src_cfg);
6608 src.crs = CrsInfo::from_epsg(4326);
6609 for row in 0..src.rows {
6610 for col in 0..src.cols {
6611 src.set(0, row as isize, col as isize, (row * src.cols + col) as f64)
6612 .unwrap();
6613 }
6614 }
6615
6616 let mut reference = Raster::new(RasterConfig {
6617 cols: 4,
6618 rows: 3,
6619 x_min: 0.0,
6620 y_min: 0.0,
6621 cell_size: 1.0,
6622 cell_size_y: Some(1.0),
6623 nodata: -9999.0,
6624 ..Default::default()
6625 });
6626 reference.crs = CrsInfo::from_epsg(4326);
6627
6628 let out = src
6629 .reproject_to_match_resolution_in_epsg(3857, &reference, ResampleMethod::Nearest)
6630 .unwrap();
6631
6632 assert_eq!(out.crs.epsg, Some(3857));
6633 assert!(out.cell_size_x.is_finite());
6634 assert!(out.cell_size_y.is_finite());
6635 assert!(out.cell_size_x > 0.0);
6636 assert!(out.cell_size_y > 0.0);
6637 }
6638
6639 #[test]
6640 fn reproject_to_epsg_bilinear_cubic_and_lanczos_produce_valid_output() {
6641 let cfg = RasterConfig {
6642 cols: 8,
6643 rows: 8,
6644 x_min: -2.0,
6645 y_min: -2.0,
6646 cell_size: 0.5,
6647 nodata: -9999.0,
6648 ..Default::default()
6649 };
6650 let mut r = Raster::new(cfg);
6651 r.crs = CrsInfo::from_epsg(4326);
6652 for row in 0..8 {
6653 for col in 0..8 {
6654 let val = (col as f64) * 10.0 + row as f64;
6655 r.set(0, row, col, val).unwrap();
6656 }
6657 }
6658
6659 let bilinear = r.reproject_to_epsg_bilinear(3857).unwrap();
6660 let cubic = r.reproject_to_epsg_cubic(3857).unwrap();
6661 let lanczos = r.reproject_to_epsg_lanczos(3857).unwrap();
6662
6663 assert_eq!(bilinear.cols, 8);
6664 assert_eq!(bilinear.rows, 8);
6665 assert_eq!(bilinear.crs.epsg, Some(3857));
6666 assert!(bilinear.statistics().valid_count > 0);
6667
6668 assert_eq!(cubic.cols, 8);
6669 assert_eq!(cubic.rows, 8);
6670 assert_eq!(cubic.crs.epsg, Some(3857));
6671 assert!(cubic.statistics().valid_count > 0);
6672
6673 assert_eq!(lanczos.cols, 8);
6674 assert_eq!(lanczos.rows, 8);
6675 assert_eq!(lanczos.crs.epsg, Some(3857));
6676 assert!(lanczos.statistics().valid_count > 0);
6677 }
6678
6679 #[test]
6680 fn reproject_with_options_honors_grid_controls() {
6681 let cfg = RasterConfig {
6682 cols: 6,
6683 rows: 4,
6684 x_min: -3.0,
6685 y_min: -2.0,
6686 cell_size: 1.0,
6687 nodata: -9999.0,
6688 ..Default::default()
6689 };
6690 let mut r = Raster::new(cfg);
6691 r.crs = CrsInfo::from_epsg(4326);
6692 for row in 0..4 {
6693 for col in 0..6 {
6694 r.set(0, row, col, (row * 6 + col) as f64).unwrap();
6695 }
6696 }
6697
6698 let opts = ReprojectOptions {
6699 dst_epsg: 3857,
6700 resample: ResampleMethod::Bilinear,
6701 cols: Some(12),
6702 rows: Some(10),
6703 extent: Some(Extent {
6704 x_min: -500_000.0,
6705 y_min: -400_000.0,
6706 x_max: 500_000.0,
6707 y_max: 400_000.0,
6708 }),
6709 x_res: None,
6710 y_res: None,
6711 snap_x: None,
6712 snap_y: None,
6713 nodata_policy: NodataPolicy::PartialKernel,
6714 antimeridian_policy: AntimeridianPolicy::Auto,
6715 grid_size_policy: GridSizePolicy::Expand,
6716 destination_footprint: DestinationFootprint::None,
6717 warn_on_area_of_use_mismatch: false,
6718 epoch_transform: EpochTransformOptions::default(),
6719 };
6720
6721 let out = r.reproject_with_options(&opts).unwrap();
6722 assert_eq!(out.cols, 12);
6723 assert_eq!(out.rows, 10);
6724 assert!((out.x_min - (-500_000.0)).abs() < 1e-9);
6725 assert!((out.y_min - (-400_000.0)).abs() < 1e-9);
6726 assert!((out.x_max() - 500_000.0).abs() < 1e-6);
6727 assert!((out.y_max() - 400_000.0).abs() < 1e-6);
6728 assert_eq!(out.crs.epsg, Some(3857));
6729 }
6730
6731 #[test]
6732 fn bilinear_partial_kernel_renormalizes_with_nodata() {
6733 let cfg = RasterConfig {
6734 cols: 2,
6735 rows: 2,
6736 x_min: 0.0,
6737 y_min: 0.0,
6738 cell_size: 1.0,
6739 nodata: -9999.0,
6740 ..Default::default()
6741 };
6742 let r = Raster::from_data(cfg, vec![1.0, -9999.0, 3.0, 5.0]).unwrap();
6744
6745 let v = r.sample_bilinear_partial_pixel(0, 0.5, 0.5).unwrap();
6746 assert!((v - 3.0).abs() < 1e-9);
6748 }
6749
6750 #[test]
6751 fn bilinear_partial_kernel_handles_edges() {
6752 let cfg = RasterConfig {
6753 cols: 2,
6754 rows: 2,
6755 x_min: 0.0,
6756 y_min: 0.0,
6757 cell_size: 1.0,
6758 nodata: -9999.0,
6759 ..Default::default()
6760 };
6761 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
6762 let v = r.sample_bilinear_partial_pixel(0, -0.2, 0.3).unwrap();
6763 assert!(v.is_finite());
6764 }
6765
6766 #[test]
6767 fn cubic_partial_kernel_handles_edges_and_nodata() {
6768 let cfg = RasterConfig {
6769 cols: 4,
6770 rows: 4,
6771 x_min: 0.0,
6772 y_min: 0.0,
6773 cell_size: 1.0,
6774 nodata: -9999.0,
6775 ..Default::default()
6776 };
6777 let mut data = Vec::new();
6778 for row in 0..4 {
6779 for col in 0..4 {
6780 data.push((row * 4 + col) as f64);
6781 }
6782 }
6783 data[5] = -9999.0; let r = Raster::from_data(cfg, data).unwrap();
6785
6786 let edge_v = r.sample_cubic_partial_pixel(0, -0.25, 0.2).unwrap();
6787 assert!(edge_v.is_finite());
6788
6789 let nodata_v = r.sample_cubic_partial_pixel(0, 1.25, 1.25).unwrap();
6790 assert!(nodata_v.is_finite());
6791 }
6792
6793 #[test]
6794 fn strict_policy_rejects_incomplete_bilinear_kernel() {
6795 let cfg = RasterConfig {
6796 cols: 2,
6797 rows: 2,
6798 x_min: 0.0,
6799 y_min: 0.0,
6800 cell_size: 1.0,
6801 nodata: -9999.0,
6802 ..Default::default()
6803 };
6804 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
6805
6806 assert!(r.sample_bilinear_strict_pixel(0, -0.2, 0.3).is_none());
6807 assert!(r.sample_bilinear_partial_pixel(0, -0.2, 0.3).is_some());
6808 }
6809
6810 #[test]
6811 fn bilinear_strict_simd_matches_expected_for_f64_and_f32_storage() {
6812 let cfg_f64 = RasterConfig {
6813 cols: 2,
6814 rows: 2,
6815 x_min: 0.0,
6816 y_min: 0.0,
6817 cell_size: 1.0,
6818 nodata: -9999.0,
6819 data_type: DataType::F64,
6820 ..Default::default()
6821 };
6822 let cfg_f32 = RasterConfig {
6823 data_type: DataType::F32,
6824 ..cfg_f64.clone()
6825 };
6826 let data = vec![1.0, 2.0, 3.0, 5.0];
6827 let r64 = Raster::from_data(cfg_f64, data.clone()).unwrap();
6828 let r32 = Raster::from_data(cfg_f32, data).unwrap();
6829
6830 let expected = 2.375;
6831 let v64 = r64.sample_bilinear_strict_pixel(0, 0.25, 0.5).unwrap();
6832 let v32 = r32.sample_bilinear_strict_pixel(0, 0.25, 0.5).unwrap();
6833 assert!((v64 - expected).abs() < 1e-9);
6834 assert!((v32 - expected).abs() < 1e-6);
6835 }
6836
6837 #[test]
6838 fn lanczos_strict_simd_matches_between_f64_and_f32_storage() {
6839 let cfg_f64 = RasterConfig {
6840 cols: 16,
6841 rows: 16,
6842 x_min: 0.0,
6843 y_min: 0.0,
6844 cell_size: 1.0,
6845 nodata: -9999.0,
6846 data_type: DataType::F64,
6847 ..Default::default()
6848 };
6849 let cfg_f32 = RasterConfig {
6850 data_type: DataType::F32,
6851 ..cfg_f64.clone()
6852 };
6853
6854 let mut data = Vec::with_capacity(16 * 16);
6855 for row in 0..16 {
6856 for col in 0..16 {
6857 data.push(((row * 16 + col) as f64).sin() * 100.0 + (row as f64 * 0.5));
6858 }
6859 }
6860
6861 let r64 = Raster::from_data(cfg_f64, data.clone()).unwrap();
6862 let r32 = Raster::from_data(cfg_f32, data).unwrap();
6863
6864 let v64 = r64.sample_lanczos_strict_pixel(0, 7.25, 8.5).unwrap();
6865 let v32 = r32.sample_lanczos_strict_pixel(0, 7.25, 8.5).unwrap();
6866 assert!((v64 - v32).abs() < 1e-4);
6867 }
6868
6869 #[test]
6870 fn fill_policy_falls_back_to_nearest_for_bilinear() {
6871 let cfg = RasterConfig {
6872 cols: 2,
6873 rows: 2,
6874 x_min: 0.0,
6875 y_min: 0.0,
6876 cell_size: 1.0,
6877 nodata: -9999.0,
6878 ..Default::default()
6879 };
6880 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
6881
6882 let v = r.sample_world(0, 0.1, 1.1, ResampleMethod::Bilinear, NodataPolicy::Fill);
6883 assert_eq!(v, Some(10.0));
6884 }
6885
6886 #[test]
6887 fn fill_policy_falls_back_to_nearest_for_lanczos() {
6888 let cfg = RasterConfig {
6889 cols: 2,
6890 rows: 2,
6891 x_min: 0.0,
6892 y_min: 0.0,
6893 cell_size: 1.0,
6894 nodata: -9999.0,
6895 ..Default::default()
6896 };
6897 let r = Raster::from_data(cfg, vec![10.0, 20.0, 30.0, 40.0]).unwrap();
6898
6899 let v = r.sample_world(0, 0.1, 1.1, ResampleMethod::Lanczos, NodataPolicy::Fill);
6900 assert_eq!(v, Some(10.0));
6901 }
6902
6903 #[test]
6904 fn reproject_options_default_to_partial_kernel_policy() {
6905 let opts = ReprojectOptions::new(3857, ResampleMethod::Cubic);
6906 assert_eq!(opts.nodata_policy, NodataPolicy::PartialKernel);
6907 assert_eq!(opts.x_res, None);
6908 assert_eq!(opts.y_res, None);
6909 assert_eq!(opts.snap_x, None);
6910 assert_eq!(opts.snap_y, None);
6911 assert_eq!(opts.antimeridian_policy, AntimeridianPolicy::Auto);
6912 assert_eq!(opts.grid_size_policy, GridSizePolicy::Expand);
6913 assert_eq!(opts.destination_footprint, DestinationFootprint::None);
6914
6915 let strict = opts.with_nodata_policy(NodataPolicy::Strict);
6916 assert_eq!(strict.nodata_policy, NodataPolicy::Strict);
6917
6918 let sized = strict.with_size(321, 123);
6919 assert_eq!(sized.cols, Some(321));
6920 assert_eq!(sized.rows, Some(123));
6921
6922 let e = Extent {
6923 x_min: -10.0,
6924 y_min: -5.0,
6925 x_max: 10.0,
6926 y_max: 5.0,
6927 };
6928 let ext = sized.with_extent(e);
6929 assert_eq!(ext.extent, Some(e));
6930
6931 let res = ext.with_resolution(30.0, 40.0);
6932 assert_eq!(res.x_res, Some(30.0));
6933 assert_eq!(res.y_res, Some(40.0));
6934
6935 let square = res.with_square_resolution(25.0);
6936 assert_eq!(square.x_res, Some(25.0));
6937 assert_eq!(square.y_res, Some(25.0));
6938
6939 let snapped = square.with_snap_origin(0.0, 0.0);
6940 assert_eq!(snapped.snap_x, Some(0.0));
6941 assert_eq!(snapped.snap_y, Some(0.0));
6942
6943 let linear = snapped.with_antimeridian_policy(AntimeridianPolicy::Linear);
6944 assert_eq!(linear.antimeridian_policy, AntimeridianPolicy::Linear);
6945
6946 let fit_inside = linear.with_grid_size_policy(GridSizePolicy::FitInside);
6947 assert_eq!(fit_inside.grid_size_policy, GridSizePolicy::FitInside);
6948
6949 let masked = fit_inside.with_destination_footprint(DestinationFootprint::SourceBoundary);
6950 assert_eq!(
6951 masked.destination_footprint,
6952 DestinationFootprint::SourceBoundary
6953 );
6954 }
6955
6956 #[test]
6957 fn sample_extent_boundary_ring_count_and_corners() {
6958 let e = Extent {
6959 x_min: 0.0,
6960 y_min: 0.0,
6961 x_max: 10.0,
6962 y_max: 5.0,
6963 };
6964 let ring = sample_extent_boundary_ring(e, 8);
6965 assert_eq!(ring.len(), 32);
6966 assert!(ring.contains(&(0.0, 0.0)));
6967 assert!(ring.contains(&(10.0, 0.0)));
6968 assert!(ring.contains(&(10.0, 5.0)));
6969 assert!(ring.contains(&(0.0, 5.0)));
6970 }
6971
6972 #[test]
6973 fn point_in_polygon_identifies_inside_and_outside_points() {
6974 let poly = vec![(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)];
6975 assert!(point_in_polygon(2.0, 2.0, &poly));
6976 assert!(!point_in_polygon(5.0, 2.0, &poly));
6977 }
6978
6979 #[test]
6980 fn thematic_3x3_resamplers_return_expected_statistics() {
6981 let cfg = RasterConfig {
6982 cols: 3,
6983 rows: 3,
6984 x_min: 0.0,
6985 y_min: 0.0,
6986 cell_size: 1.0,
6987 nodata: -9999.0,
6988 ..Default::default()
6989 };
6990 let data = vec![1.0, 2.0, 2.0, 3.0, 4.0, 4.0, 5.0, 6.0, 6.0];
6991 let expected_mean = data.iter().sum::<f64>() / data.len() as f64;
6992 let expected_stddev = (data
6993 .iter()
6994 .map(|v| {
6995 let d = *v - expected_mean;
6996 d * d
6997 })
6998 .sum::<f64>()
6999 / data.len() as f64)
7000 .sqrt();
7001 let r = Raster::from_data(cfg, data).unwrap();
7002
7003 let x = r.col_center_x(1);
7004 let y = r.row_center_y(1);
7005
7006 let avg = r
7007 .sample_world(0, x, y, ResampleMethod::Average, NodataPolicy::Strict)
7008 .unwrap();
7009 let min = r
7010 .sample_world(0, x, y, ResampleMethod::Min, NodataPolicy::Strict)
7011 .unwrap();
7012 let max = r
7013 .sample_world(0, x, y, ResampleMethod::Max, NodataPolicy::Strict)
7014 .unwrap();
7015 let mode = r
7016 .sample_world(0, x, y, ResampleMethod::Mode, NodataPolicy::Strict)
7017 .unwrap();
7018 let median = r
7019 .sample_world(0, x, y, ResampleMethod::Median, NodataPolicy::Strict)
7020 .unwrap();
7021 let stddev = r
7022 .sample_world(0, x, y, ResampleMethod::StdDev, NodataPolicy::Strict)
7023 .unwrap();
7024
7025 assert!((avg - (33.0 / 9.0)).abs() < 1e-9);
7026 assert_eq!(min, 1.0);
7027 assert_eq!(max, 6.0);
7028 assert_eq!(mode, 2.0);
7029 assert_eq!(median, 4.0);
7030 assert!((stddev - expected_stddev).abs() < 1e-9);
7031 }
7032
7033 #[test]
7034 fn grid_size_policy_fit_inside_reduces_resolution_derived_size() {
7035 let mut r = make_raster();
7036 r.crs = CrsInfo::from_epsg(4326);
7037
7038 let extent = Extent {
7039 x_min: -500_000.0,
7040 y_min: -400_000.0,
7041 x_max: 500_000.0,
7042 y_max: 400_000.0,
7043 };
7044
7045 let expand = r
7046 .reproject_with_options(
7047 &ReprojectOptions::new(3857, ResampleMethod::Nearest)
7048 .with_extent(extent)
7049 .with_resolution(300_000.0, 300_000.0)
7050 .with_grid_size_policy(GridSizePolicy::Expand),
7051 )
7052 .unwrap();
7053
7054 let fit = r
7055 .reproject_with_options(
7056 &ReprojectOptions::new(3857, ResampleMethod::Nearest)
7057 .with_extent(extent)
7058 .with_resolution(300_000.0, 300_000.0)
7059 .with_grid_size_policy(GridSizePolicy::FitInside),
7060 )
7061 .unwrap();
7062
7063 assert!(fit.cols <= expand.cols);
7064 assert!(fit.rows <= expand.rows);
7065 }
7066
7067 #[test]
7068 fn destination_footprint_masks_cells_outside_source_boundary() {
7069 let cfg = RasterConfig {
7070 cols: 4,
7071 rows: 4,
7072 x_min: 0.0,
7073 y_min: 0.0,
7074 cell_size: 1.0,
7075 nodata: -9999.0,
7076 ..Default::default()
7077 };
7078 let mut r = Raster::new(cfg);
7079 r.crs = CrsInfo::from_epsg(4326);
7080 for row in 0..4 {
7081 for col in 0..4 {
7082 r.set(0, row, col, 1.0).unwrap();
7083 }
7084 }
7085
7086 let out = r
7087 .reproject_with_options(
7088 &ReprojectOptions::new(4326, ResampleMethod::Nearest)
7089 .with_extent(Extent {
7090 x_min: -1.0,
7091 y_min: -1.0,
7092 x_max: 5.0,
7093 y_max: 5.0,
7094 })
7095 .with_size(6, 6)
7096 .with_destination_footprint(DestinationFootprint::SourceBoundary),
7097 )
7098 .unwrap();
7099
7100 assert!(out.is_nodata(out.get(0, 0, 0)));
7101 assert!(!out.is_nodata(out.get(0, 2, 2)));
7102 }
7103
7104 #[test]
7105 fn reproject_with_options_honors_snap_origin_with_resolution() {
7106 let cfg = RasterConfig {
7107 cols: 6,
7108 rows: 4,
7109 x_min: -3.0,
7110 y_min: -2.0,
7111 cell_size: 1.0,
7112 nodata: -9999.0,
7113 ..Default::default()
7114 };
7115 let mut r = Raster::new(cfg);
7116 r.crs = CrsInfo::from_epsg(4326);
7117 for row in 0..4 {
7118 for col in 0..6 {
7119 r.set(0, row, col, (row * 6 + col) as f64).unwrap();
7120 }
7121 }
7122
7123 let out_extent = Extent {
7124 x_min: -510_000.0,
7125 y_min: -390_000.0,
7126 x_max: 490_000.0,
7127 y_max: 410_000.0,
7128 };
7129 let opts = ReprojectOptions::new(3857, ResampleMethod::Bilinear)
7130 .with_extent(out_extent)
7131 .with_resolution(200_000.0, 160_000.0)
7132 .with_snap_origin(0.0, 0.0);
7133
7134 let out = r.reproject_with_options(&opts).unwrap();
7135 assert!((out.x_min - (-600_000.0)).abs() < 1e-6);
7136 assert!((out.y_min - (-480_000.0)).abs() < 1e-6);
7137 assert!((out.cell_size_x - 200_000.0).abs() < 1e-6);
7138 assert!((out.cell_size_y - 160_000.0).abs() < 1e-6);
7139 }
7140
7141 #[test]
7142 fn reproject_with_options_honors_resolution_controls() {
7143 let cfg = RasterConfig {
7144 cols: 6,
7145 rows: 4,
7146 x_min: -3.0,
7147 y_min: -2.0,
7148 cell_size: 1.0,
7149 nodata: -9999.0,
7150 ..Default::default()
7151 };
7152 let mut r = Raster::new(cfg);
7153 r.crs = CrsInfo::from_epsg(4326);
7154 for row in 0..4 {
7155 for col in 0..6 {
7156 r.set(0, row, col, (row * 6 + col) as f64).unwrap();
7157 }
7158 }
7159
7160 let out_extent = Extent {
7161 x_min: -500_000.0,
7162 y_min: -400_000.0,
7163 x_max: 500_000.0,
7164 y_max: 400_000.0,
7165 };
7166 let opts = ReprojectOptions::new(3857, ResampleMethod::Bilinear)
7167 .with_extent(out_extent)
7168 .with_resolution(200_000.0, 160_000.0);
7169
7170 let out = r.reproject_with_options(&opts).unwrap();
7171 assert_eq!(out.cols, 5);
7172 assert_eq!(out.rows, 5);
7173 assert!((out.cell_size_x - 200_000.0).abs() < 1e-6);
7174 assert!((out.cell_size_y - 160_000.0).abs() < 1e-6);
7175 }
7176
7177 #[test]
7178 fn reproject_with_options_rejects_invalid_resolution_controls() {
7179 let mut r = make_raster();
7180 r.crs = CrsInfo::from_epsg(4326);
7181 let opts = ReprojectOptions::new(3857, ResampleMethod::Nearest).with_square_resolution(0.0);
7182 let err = r.reproject_with_options(&opts).unwrap_err();
7183 assert!(err.to_string().contains("invalid reprojection resolution"));
7184 }
7185
7186 #[test]
7187 fn band_helpers() {
7188 let cfg = RasterConfig {
7189 cols: 3,
7190 rows: 2,
7191 bands: 2,
7192 nodata: -9999.0,
7193 ..Default::default()
7194 };
7195 let data = vec![
7196 1.0, 2.0, 3.0, 4.0, 5.0, -9999.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0,
7197 ];
7198 let mut r = Raster::from_data(cfg, data).unwrap();
7199
7200 let b0 = r.band_slice(0);
7201 assert_eq!(b0.len(), 6);
7202 assert_eq!(b0[0], 1.0);
7203 assert_eq!(b0[5], -9999.0);
7204
7205 let s0 = r.statistics_band(0).unwrap();
7206 assert_eq!(s0.valid_count, 5);
7207 assert_eq!(s0.nodata_count, 1);
7208 assert_eq!(s0.min, 1.0);
7209 assert_eq!(s0.max, 5.0);
7210
7211 r.set_band_slice(1, &[7.0, 8.0, 9.0, 10.0, 11.0, 12.0])
7212 .unwrap();
7213 assert_eq!(r.get_raw(1, 0, 0), Some(7.0));
7214 assert_eq!(r.get_raw(1, 1, 2), Some(12.0));
7215 }
7216
7217 #[test]
7218 fn band_transform_helpers() {
7219 let cfg = RasterConfig {
7220 cols: 2,
7221 rows: 2,
7222 bands: 2,
7223 nodata: -9999.0,
7224 ..Default::default()
7225 };
7226 let data = vec![1.0, 2.0, 3.0, -9999.0, 10.0, 20.0, 30.0, 40.0];
7227 let mut r = Raster::from_data(cfg, data).unwrap();
7228
7229 r.map_valid_band(0, |v| v * 2.0).unwrap();
7230 assert_eq!(r.get_raw(0, 0, 0), Some(2.0));
7231 assert_eq!(r.get_raw(0, 0, 1), Some(4.0));
7232 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();
7236 assert_eq!(r.get_raw(1, 0, 1), Some(99.0));
7237 assert_eq!(r.get_raw(1, 0, 0), Some(10.0));
7238 assert_eq!(r.get_raw(0, 0, 1), Some(4.0));
7239 }
7240
7241 #[test]
7242 fn band_iterators() {
7243 let cfg = RasterConfig {
7244 cols: 3,
7245 rows: 2,
7246 bands: 2,
7247 nodata: -9999.0,
7248 ..Default::default()
7249 };
7250 let data = vec![
7251 1.0, 2.0, -9999.0, 4.0, 5.0, 6.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0,
7252 ];
7253 let r = Raster::from_data(cfg, data).unwrap();
7254
7255 let valid_b0: Vec<_> = r.iter_valid_band(0).unwrap().collect();
7256 assert_eq!(valid_b0.len(), 5);
7257 assert_eq!(valid_b0[0], (0, 0, 1.0));
7258 assert_eq!(valid_b0[4], (1, 2, 6.0));
7259
7260 let rows_b1: Vec<Vec<f64>> = r.iter_band_rows(1).unwrap().collect();
7261 assert_eq!(rows_b1.len(), 2);
7262 assert_eq!(rows_b1[0], vec![10.0, 20.0, 30.0]);
7263 assert_eq!(rows_b1[1], vec![40.0, 50.0, 60.0]);
7264 }
7265
7266 #[test]
7267 fn mutable_band_rows_native() {
7268 let cfg = RasterConfig {
7269 cols: 3,
7270 rows: 2,
7271 bands: 1,
7272 data_type: DataType::F32,
7273 nodata: -9999.0,
7274 ..Default::default()
7275 };
7276 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
7277 let mut r = Raster::from_data(cfg, data).unwrap();
7278
7279 r.for_each_band_row_mut(0, |row, row_mut| {
7280 if let RasterRowMut::F32(slice) = row_mut {
7281 for v in slice.iter_mut() {
7282 *v += row as f32;
7283 }
7284 }
7285 })
7286 .unwrap();
7287
7288 assert_eq!(r.get_raw(0, 0, 0), Some(1.0));
7289 assert_eq!(r.get_raw(0, 0, 2), Some(3.0));
7290 assert_eq!(r.get_raw(0, 1, 0), Some(5.0));
7291 assert_eq!(r.get_raw(0, 1, 2), Some(7.0));
7292 }
7293
7294 #[test]
7295 fn immutable_band_rows_native() {
7296 let cfg = RasterConfig {
7297 cols: 3,
7298 rows: 2,
7299 bands: 1,
7300 data_type: DataType::U16,
7301 nodata: 0.0,
7302 ..Default::default()
7303 };
7304 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
7305 let r = Raster::from_data(cfg, data).unwrap();
7306
7307 let mut sums = Vec::new();
7308 r.for_each_band_row(0, |_row, row_ref| {
7309 if let RasterRowRef::U16(slice) = row_ref {
7310 sums.push(slice.iter().map(|v| *v as u64).sum::<u64>());
7311 }
7312 })
7313 .unwrap();
7314
7315 assert_eq!(sums, vec![6, 15]);
7316 }
7317}