1use ndarray::ArrayD;
7
8use crate::error::{Error, Result};
9use crate::types::{NcType, NcVariable};
10
11use super::data::{self, compute_record_stride, NcReadType};
12use super::ClassicFile;
13
14impl ClassicFile {
15 pub fn read_variable<T: NcReadType>(&self, name: &str) -> Result<ArrayD<T>> {
20 let var = self.find_variable(name)?;
21
22 let expected = T::nc_type();
24 if var.dtype != expected {
25 return Err(Error::TypeMismatch {
26 expected: format!("{:?}", expected),
27 actual: format!("{:?}", var.dtype),
28 });
29 }
30
31 let file_data = self.data.as_slice();
32
33 if var.is_record_var {
34 let record_stride = compute_record_stride(&self.root_group.variables);
35 data::read_record_variable(file_data, var, self.numrecs, record_stride)
36 } else {
37 data::read_non_record_variable(file_data, var)
38 }
39 }
40
41 pub fn read_variable_as_f64(&self, name: &str) -> Result<ArrayD<f64>> {
46 let var = self.find_variable(name)?;
47 let file_data = self.data.as_slice();
48
49 match var.dtype {
50 NcType::Byte => {
51 let arr = self.read_typed_variable::<i8>(var, file_data)?;
52 Ok(arr.mapv(|v| v as f64))
53 }
54 NcType::Short => {
55 let arr = self.read_typed_variable::<i16>(var, file_data)?;
56 Ok(arr.mapv(|v| v as f64))
57 }
58 NcType::Int => {
59 let arr = self.read_typed_variable::<i32>(var, file_data)?;
60 Ok(arr.mapv(|v| v as f64))
61 }
62 NcType::Float => {
63 let arr = self.read_typed_variable::<f32>(var, file_data)?;
64 Ok(arr.mapv(|v| v as f64))
65 }
66 NcType::Double => self.read_typed_variable::<f64>(var, file_data),
67 NcType::UByte => {
68 let arr = self.read_typed_variable::<u8>(var, file_data)?;
69 Ok(arr.mapv(|v| v as f64))
70 }
71 NcType::UShort => {
72 let arr = self.read_typed_variable::<u16>(var, file_data)?;
73 Ok(arr.mapv(|v| v as f64))
74 }
75 NcType::UInt => {
76 let arr = self.read_typed_variable::<u32>(var, file_data)?;
77 Ok(arr.mapv(|v| v as f64))
78 }
79 NcType::Int64 => {
80 let arr = self.read_typed_variable::<i64>(var, file_data)?;
81 Ok(arr.mapv(|v| v as f64))
82 }
83 NcType::UInt64 => {
84 let arr = self.read_typed_variable::<u64>(var, file_data)?;
85 Ok(arr.mapv(|v| v as f64))
86 }
87 NcType::Char => Err(Error::TypeMismatch {
88 expected: "numeric type".to_string(),
89 actual: "Char".to_string(),
90 }),
91 NcType::String => Err(Error::TypeMismatch {
92 expected: "numeric type".to_string(),
93 actual: "String".to_string(),
94 }),
95 _ => Err(Error::TypeMismatch {
96 expected: "numeric type".to_string(),
97 actual: format!("{:?}", var.dtype),
98 }),
99 }
100 }
101
102 pub fn read_variable_as_string(&self, name: &str) -> Result<String> {
104 let var = self.find_variable(name)?;
105 if var.dtype != NcType::Char {
106 return Err(Error::TypeMismatch {
107 expected: "Char".to_string(),
108 actual: format!("{:?}", var.dtype),
109 });
110 }
111
112 let file_data = self.data.as_slice();
113 let arr = self.read_typed_variable::<u8>(var, file_data)?;
114 let bytes: Vec<u8> = arr.iter().copied().collect();
115 let s = String::from_utf8_lossy(&bytes)
116 .trim_end_matches('\0')
117 .to_string();
118 Ok(s)
119 }
120
121 pub fn read_variable_slice<T: NcReadType>(
128 &self,
129 name: &str,
130 selection: &crate::types::NcSliceInfo,
131 ) -> Result<ArrayD<T>> {
132 use crate::types::NcSliceInfoElem;
133 use ndarray::IxDyn;
134
135 let var = self.find_variable(name)?;
136 let expected = T::nc_type();
137 if var.dtype != expected {
138 return Err(Error::TypeMismatch {
139 expected: format!("{:?}", expected),
140 actual: format!("{:?}", var.dtype),
141 });
142 }
143 let file_data = self.data.as_slice();
144
145 if !var.is_record_var && var.ndim() > 0 {
147 let shape: Vec<u64> = var.shape();
148 let ndim = shape.len();
149
150 let can_direct = selection.selections.len() == ndim
152 && selection
153 .selections
154 .iter()
155 .enumerate()
156 .skip(1)
157 .all(|(d, sel)| {
158 matches!(sel, NcSliceInfoElem::Slice { start, end, step }
159 if *start == 0 && *step == 1 && (*end == u64::MAX || *end >= shape[d]))
160 });
161
162 if can_direct {
163 let elem_size = T::element_size();
164 let row_elements = crate::types::checked_shape_elements(
165 &shape[1..],
166 "classic slice row element count",
167 )?
168 .max(1);
169 let row_bytes = crate::types::checked_usize_from_u64(
170 crate::types::checked_mul_u64(
171 row_elements,
172 elem_size as u64,
173 "classic slice row size in bytes",
174 )?,
175 "classic slice row size in bytes",
176 )?;
177
178 let (first_row, num_rows, result_shape) = match &selection.selections[0] {
179 NcSliceInfoElem::Index(idx) => {
180 let rs: Vec<usize> = shape[1..]
181 .iter()
182 .map(|&d| {
183 crate::types::checked_usize_from_u64(
184 d,
185 "classic slice result dimension",
186 )
187 })
188 .collect::<Result<Vec<_>>>()?;
189 (*idx, 1u64, rs)
190 }
191 NcSliceInfoElem::Slice { start, end, step } => {
192 let actual_end = if *end == u64::MAX {
193 shape[0]
194 } else {
195 (*end).min(shape[0])
196 };
197 let count = (actual_end - start).div_ceil(*step);
198 if *step == 1 {
199 let mut rs = vec![crate::types::checked_usize_from_u64(
200 count,
201 "classic slice result dimension",
202 )?];
203 rs.extend(
204 shape[1..]
205 .iter()
206 .map(|&d| {
207 crate::types::checked_usize_from_u64(
208 d,
209 "classic slice result dimension",
210 )
211 })
212 .collect::<Result<Vec<_>>>()?,
213 );
214 (*start, count, rs)
215 } else {
216 return {
218 let full = data::read_non_record_variable(file_data, var)?;
219 slice_classic_array(&full, var, selection, 0)
220 };
221 }
222 }
223 };
224
225 let byte_offset = crate::types::checked_usize_from_u64(
226 var.data_offset,
227 "classic slice data offset",
228 )?
229 .checked_add(
230 crate::types::checked_usize_from_u64(first_row, "classic slice row offset")?
231 .checked_mul(row_bytes)
232 .ok_or_else(|| {
233 Error::InvalidData(
234 "classic slice byte offset exceeds platform usize".to_string(),
235 )
236 })?,
237 )
238 .ok_or_else(|| {
239 Error::InvalidData(
240 "classic slice byte offset exceeds platform usize".to_string(),
241 )
242 })?;
243 let total_bytes =
244 crate::types::checked_usize_from_u64(num_rows, "classic slice row count")?
245 .checked_mul(row_bytes)
246 .ok_or_else(|| {
247 Error::InvalidData(
248 "classic slice byte count exceeds platform usize".to_string(),
249 )
250 })?;
251 let total_elements = crate::types::checked_usize_from_u64(
252 crate::types::checked_mul_u64(
253 num_rows,
254 row_elements,
255 "classic slice element count",
256 )?,
257 "classic slice element count",
258 )?;
259
260 let end = byte_offset.checked_add(total_bytes).ok_or_else(|| {
261 Error::InvalidData(
262 "classic slice byte range exceeds platform usize".to_string(),
263 )
264 })?;
265 if end > file_data.len() {
266 return Err(Error::InvalidData(format!(
267 "variable '{}' slice data extends beyond file",
268 var.name
269 )));
270 }
271
272 let data_slice = &file_data[byte_offset..end];
273 let values = T::decode_bulk_be(data_slice, total_elements)?;
274
275 return ndarray::ArrayD::from_shape_vec(IxDyn(&result_shape), values)
276 .map_err(|e| Error::InvalidData(format!("failed to create array: {}", e)));
277 }
278 }
279
280 if var.is_record_var {
282 let record_stride = compute_record_stride(&self.root_group.variables);
283 let full = data::read_record_variable(file_data, var, self.numrecs, record_stride)?;
284 slice_classic_array(&full, var, selection, self.numrecs)
285 } else {
286 let full = data::read_non_record_variable(file_data, var)?;
287 slice_classic_array(&full, var, selection, 0)
288 }
289 }
290
291 pub fn read_variable_slice_as_f64(
293 &self,
294 name: &str,
295 selection: &crate::types::NcSliceInfo,
296 ) -> Result<ArrayD<f64>> {
297 let var = self.find_variable(name)?;
298 let file_data = self.data.as_slice();
299
300 macro_rules! slice_promoted {
301 ($ty:ty) => {{
302 let full = self.read_typed_variable::<$ty>(var, file_data)?;
303 let full_f64 = full.mapv(|v| v as f64);
304 slice_classic_array(
305 &full_f64,
306 var,
307 selection,
308 if var.is_record_var { self.numrecs } else { 0 },
309 )
310 }};
311 }
312
313 match var.dtype {
314 NcType::Byte => slice_promoted!(i8),
315 NcType::Short => slice_promoted!(i16),
316 NcType::Int => slice_promoted!(i32),
317 NcType::Float => slice_promoted!(f32),
318 NcType::Double => slice_promoted!(f64),
319 NcType::UByte => slice_promoted!(u8),
320 NcType::UShort => slice_promoted!(u16),
321 NcType::UInt => slice_promoted!(u32),
322 NcType::Int64 => slice_promoted!(i64),
323 NcType::UInt64 => slice_promoted!(u64),
324 NcType::Char => Err(Error::TypeMismatch {
325 expected: "numeric type".to_string(),
326 actual: "Char".to_string(),
327 }),
328 _ => Err(Error::TypeMismatch {
329 expected: "numeric type".to_string(),
330 actual: format!("{:?}", var.dtype),
331 }),
332 }
333 }
334
335 fn find_variable(&self, name: &str) -> Result<&NcVariable> {
337 self.root_group
338 .variables
339 .iter()
340 .find(|v| v.name == name)
341 .ok_or_else(|| Error::VariableNotFound(name.to_string()))
342 }
343
344 fn read_typed_variable<T: NcReadType>(
346 &self,
347 var: &NcVariable,
348 file_data: &[u8],
349 ) -> Result<ArrayD<T>> {
350 if var.is_record_var {
351 let record_stride = compute_record_stride(&self.root_group.variables);
352 data::read_record_variable(file_data, var, self.numrecs, record_stride)
353 } else {
354 data::read_non_record_variable(file_data, var)
355 }
356 }
357}
358
359fn slice_classic_array<T: Clone + Default + 'static>(
364 full: &ArrayD<T>,
365 var: &NcVariable,
366 selection: &crate::types::NcSliceInfo,
367 numrecs: u64,
368) -> Result<ArrayD<T>> {
369 use crate::types::NcSliceInfoElem;
370 use ndarray::Slice;
371
372 let ndim = var.ndim();
373 if selection.selections.len() != ndim {
374 return Err(Error::InvalidData(format!(
375 "selection has {} dimensions but variable '{}' has {}",
376 selection.selections.len(),
377 var.name,
378 ndim
379 )));
380 }
381
382 let mut shape: Vec<usize> = var.shape().iter().map(|&s| s as usize).collect();
384 if var.is_record_var && !shape.is_empty() {
385 shape[0] = numrecs as usize;
386 }
387
388 let mut view = full.view();
390 for (d, sel) in selection.selections.iter().enumerate() {
391 let dim_size = shape[d] as u64;
392 match sel {
393 NcSliceInfoElem::Index(idx) => {
394 if *idx >= dim_size {
395 return Err(Error::InvalidData(format!(
396 "index {} out of bounds for dimension {} (size {})",
397 idx, d, dim_size
398 )));
399 }
400 view.slice_axis_inplace(
402 ndarray::Axis(d),
403 Slice::new(*idx as isize, Some(*idx as isize + 1), 1),
404 );
405 }
406 NcSliceInfoElem::Slice { start, end, step } => {
407 let actual_end = if *end == u64::MAX {
408 dim_size as isize
409 } else {
410 (*end).min(dim_size) as isize
411 };
412 view.slice_axis_inplace(
413 ndarray::Axis(d),
414 Slice::new(*start as isize, Some(actual_end), *step as isize),
415 );
416 }
417 }
418 }
419
420 let mut result = view.to_owned();
422 let mut removed = 0;
423 for (d, sel) in selection.selections.iter().enumerate() {
424 if matches!(sel, NcSliceInfoElem::Index(_)) {
425 let axis = d - removed;
426 result = result.index_axis_move(ndarray::Axis(axis), 0);
427 removed += 1;
428 }
429 }
430
431 Ok(result)
432}