1use crate::frame::Frame;
4use crate::positions::{Positions, Rung};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum CellConvention {
9 Centre,
12 Edge,
14}
15
16#[derive(Debug, Clone)]
18pub struct Mapping {
19 pub dims: Vec<usize>,
22 pub positions: Positions,
23 pub frame: Frame,
24 pub cell: CellConvention,
25 pub provenance: String,
28}
29
30impl Mapping {
31 pub fn new(dims: Vec<usize>, positions: Positions, frame: Frame) -> Self {
32 Self {
33 dims,
34 positions,
35 frame,
36 cell: CellConvention::Centre,
37 provenance: "stored".into(),
38 }
39 }
40
41 pub fn with_cell(mut self, cell: CellConvention) -> Self {
42 self.cell = cell;
43 self
44 }
45
46 pub fn with_provenance(mut self, provenance: impl Into<String>) -> Self {
47 self.provenance = provenance.into();
48 self
49 }
50
51 pub fn rung(&self) -> Rung {
52 self.positions.rung()
53 }
54
55 pub fn position(&self, index: &[f64]) -> Option<Vec<f64>> {
61 match &self.positions {
62 Positions::IndexOnly => None,
63 Positions::Affine(a) => {
64 let (x, y) = a.forward(index[1], index[0]);
65 Some(vec![x, y])
66 }
67 Positions::Regular1 { origin, step } => Some(vec![origin + index[0] * step]),
68 Positions::Rectilinear(axes) => axes
69 .iter()
70 .zip(index)
71 .map(|(c, &i)| interp_axis(c, i, self.cell))
72 .collect(),
73 Positions::Curvilinear(arrays) => {
74 let shape = &arrays.first()?.shape;
75 arrays
76 .iter()
77 .map(|a| interp_nd(&a.values, shape, index))
78 .collect()
79 }
80 Positions::Fitted(m) => m.forward(index),
81 Positions::Scheme(_) | Positions::Explicit(_) => None,
82 }
83 }
84}
85
86fn interp_axis(c: &[f64], i: f64, cell: CellConvention) -> Option<f64> {
89 if c.is_empty() || !i.is_finite() {
90 return None;
91 }
92 let s = match cell {
94 CellConvention::Centre => i - 0.5,
95 CellConvention::Edge => i,
96 };
97 if c.len() == 1 {
98 return Some(c[0]);
99 }
100 let k = (s.floor() as isize).clamp(0, c.len() as isize - 2) as usize;
101 let t = s - k as f64;
102 Some(c[k] + t * (c[k + 1] - c[k]))
103}
104
105fn interp_nd(values: &[f64], shape: &[usize], index: &[f64]) -> Option<f64> {
108 let nd = shape.len();
109 if index.len() != nd {
110 return None;
111 }
112 let mut base = vec![0usize; nd];
113 let mut frac = vec![0f64; nd];
114 for d in 0..nd {
115 let s = index[d] - 0.5;
116 if !(s >= 0.0 && s <= (shape[d] - 1) as f64) {
117 return None;
118 }
119 let k = (s.floor() as usize).min(shape[d].saturating_sub(2));
120 base[d] = k;
121 frac[d] = s - k as f64;
122 }
123 let mut strides = vec![1usize; nd];
124 for d in (0..nd.saturating_sub(1)).rev() {
125 strides[d] = strides[d + 1] * shape[d + 1];
126 }
127 let mut acc = 0.0;
128 for corner in 0..(1usize << nd) {
129 let mut w = 1.0;
130 let mut off = 0;
131 for d in 0..nd {
132 let up = (corner >> d) & 1 == 1;
133 let k = if up && shape[d] > 1 {
134 base[d] + 1
135 } else {
136 base[d]
137 };
138 w *= if up { frac[d] } else { 1.0 - frac[d] };
139 off += k * strides[d];
140 }
141 if w != 0.0 {
142 acc += w * values[off];
143 }
144 }
145 Some(acc)
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::affine::Affine;
152 use crate::positions::CoordArray;
153
154 #[test]
155 fn affine_centre_of_first_cell() {
156 let m = Mapping::new(
157 vec![0, 1],
158 Positions::Affine(Affine::from_extent([0.0, 360.0, -90.0, 90.0], [360, 180])),
159 Frame::crs("EPSG:4326"),
160 );
161 assert_eq!(m.position(&[0.5, 0.5]).unwrap(), vec![0.5, 89.5]);
162 }
163
164 #[test]
165 fn rectilinear_centres_interpolate_and_extrapolate() {
166 let m = Mapping::new(
167 vec![0],
168 Positions::Rectilinear(vec![vec![0.5, 1.5, 3.5]]),
169 Frame::Index,
170 );
171 assert_eq!(m.position(&[0.5]).unwrap(), vec![0.5]);
172 assert_eq!(m.position(&[2.5]).unwrap(), vec![3.5]);
173 assert_eq!(m.position(&[0.0]).unwrap(), vec![0.0]);
175 }
176
177 #[test]
178 fn curvilinear_bilinear() {
179 let x = CoordArray::new(vec![2, 2], vec![0.0, 1.0, 0.0, 1.0]);
181 let y = CoordArray::new(vec![2, 2], vec![0.0, 0.0, 10.0, 10.0]);
182 let m = Mapping::new(vec![0, 1], Positions::Curvilinear(vec![x, y]), Frame::Index);
183 assert_eq!(m.position(&[1.0, 1.0]).unwrap(), vec![0.5, 5.0]);
184 assert!(m.position(&[0.0, 0.0]).is_none());
185 }
186}