fits_io/bin_table/
bin_table.rs1use crate::bin_table::{FieldDefinition, Row, Value};
2use crate::header::Header;
3#[cfg(feature = "rayon")]
4use rayon::iter::{IntoParallelIterator, ParallelIterator};
5use std::error::Error;
6
7#[derive(Debug, Clone, Default)]
9pub struct BinTable {
10 data: Vec<u8>,
12 field_definitions: Vec<FieldDefinition>,
13 rows: usize,
14 bytes_per_row: usize,
15 heap_offset: usize,
17}
18
19impl BinTable {
20 pub fn push_row(&mut self, values: &[Value]) -> Result<(), Box<dyn Error + Send + Sync>> {
30 if values.len() != self.field_definitions.len() {
31 return Err(format!(
32 "This table has {} columns, but the row has {} values",
33 self.field_definitions.len(),
34 values.len()
35 )
36 .into());
37 }
38
39 if self.bytes_per_row == 0 {
42 self.bytes_per_row = self
43 .field_definitions
44 .iter()
45 .map(|field| field.format.bytes_len())
46 .sum();
47 }
48
49 for (field, value) in self.field_definitions.iter().zip(values) {
50 crate::bin_table::encode::encode(value, field.format, &mut self.data);
51 }
52
53 self.rows += 1;
54 self.heap_offset = self.data.len();
55
56 Ok(())
57 }
58
59 pub fn new(field_definitions: Vec<FieldDefinition>) -> Self {
61 Self {
62 data: vec![],
63 field_definitions,
64 rows: 0,
65 bytes_per_row: 0,
66 heap_offset: 0,
67 }
68 }
69
70 #[cfg(feature = "serde")]
76 pub(crate) fn from_parts(
77 field_definitions: Vec<FieldDefinition>,
78 data: Vec<u8>,
79 bytes_per_row: usize,
80 rows: usize,
81 heap_offset: usize,
82 ) -> Self {
83 Self {
84 heap_offset: heap_offset.min(data.len()),
85 data,
86 field_definitions,
87 bytes_per_row,
88 rows,
89 }
90 }
91
92 pub fn field_definitions(&self) -> &[FieldDefinition] {
94 &self.field_definitions
95 }
96
97 pub fn bytes_per_row(&self) -> usize {
99 self.bytes_per_row
100 }
101
102 pub fn from_u8(header: &Header, data: Vec<u8>) -> Result<Self, Box<dyn Error + Send + Sync>> {
104 if header.naxis() == Some(2) {
105 let bytes_per_row = header
106 .naxis_n(0)
107 .ok_or("Table header is missing its NAXIS1 card")?;
108 let rows = header
109 .naxis_n(1)
110 .ok_or("Table header is missing its NAXIS2 card")?;
111 let bytes_per_row = usize::try_from(bytes_per_row)
112 .map_err(|_| format!("NAXIS1 must not be negative, but was {}", bytes_per_row))?;
113 let rows = usize::try_from(rows)
114 .map_err(|_| format!("NAXIS2 must not be negative, but was {}", rows))?;
115
116 let expected = rows
117 .checked_mul(bytes_per_row)
118 .ok_or("Table dimensions overflow the address space")?;
119 if data.len() < expected {
120 return Err(format!(
121 "Data vec is too short, expected {} bytes, but data was only {} bytes long",
122 expected,
123 data.len()
124 )
125 .into());
126 }
127
128 let field_definitions = FieldDefinition::all_from_header(header)?;
129 Ok(Self {
130 heap_offset: header.table_heap_offset().min(data.len()),
131 data,
132 field_definitions,
133 bytes_per_row,
134 rows,
135 })
136 } else {
137 Err("Only two dimensions are supported.".into())
138 }
139 }
140
141 pub fn row(&'_ self, row: usize) -> Option<Row<'_>> {
143 if row < self.rows {
144 let offset = self.bytes_per_row * row;
145 let data = &self.data[offset..offset + self.bytes_per_row];
146 Some(Row::new(&self.field_definitions, data, self.heap()))
147 } else {
148 None
149 }
150 }
151
152 pub fn data(&self) -> &[u8] {
154 &self.data
155 }
156
157 pub fn heap_len(&self) -> usize {
159 self.data.len().saturating_sub(self.heap_offset)
160 }
161
162 pub fn heap(&self) -> &[u8] {
166 &self.data[self.heap_offset..]
167 }
168
169 pub fn rows(&'_ self) -> impl Iterator<Item = Row<'_>> + '_ {
171 (0..(self.rows)).filter_map(move |row| self.row(row))
172 }
173
174 #[cfg(feature = "rayon")]
176 pub fn rows_parallel(&'_ self) -> impl ParallelIterator<Item = Row<'_>> + '_ {
177 (0..(self.rows))
178 .into_par_iter()
179 .filter_map(move |row| self.row(row))
180 }
181
182 pub fn len(&self) -> usize {
184 self.rows
185 }
186
187 pub fn is_empty(&self) -> bool {
189 self.rows == 0
190 }
191}