1#![doc = include_str!("../README.md")]
2
3use std::fmt;
4
5mod common;
6mod consts;
7mod decoder;
8mod encoder;
9
10pub use consts::{TILE_FIXED_HEADER_LENGTH, TILE_VERSION_MAJOR};
11pub use decoder::{decode_payload_values, decode_tile_minimal, inspect_tile};
12pub use encoder::{encode_payload_values, encode_tile};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum MeshKind {
16 JisX0410,
17 Xyz,
18}
19
20impl MeshKind {
21 pub(crate) fn code(self) -> u8 {
22 match self {
23 Self::JisX0410 => 1,
24 Self::Xyz => 2,
25 }
26 }
27
28 pub(crate) fn from_code(code: u8) -> Result<Self> {
29 match code {
30 1 => Ok(Self::JisX0410),
31 2 => Ok(Self::Xyz),
32 _ => Err(TileError::new(
33 TileErrorCode::InvalidFieldValue,
34 format!("Invalid mesh_kind code {code}."),
35 )),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum Endianness {
42 Little,
43 Big,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum DType {
48 Uint8,
49 Int8,
50 Uint16,
51 Int16,
52 Uint32,
53 Int32,
54 Float32,
55 Float64,
56}
57
58impl DType {
59 pub(crate) fn code(self) -> u8 {
60 match self {
61 Self::Uint8 => 0,
62 Self::Int8 => 1,
63 Self::Uint16 => 2,
64 Self::Int16 => 3,
65 Self::Uint32 => 4,
66 Self::Int32 => 5,
67 Self::Float32 => 6,
68 Self::Float64 => 7,
69 }
70 }
71
72 pub(crate) fn from_code(code: u8) -> Result<Self> {
73 match code {
74 0 => Ok(Self::Uint8),
75 1 => Ok(Self::Int8),
76 2 => Ok(Self::Uint16),
77 3 => Ok(Self::Int16),
78 4 => Ok(Self::Uint32),
79 5 => Ok(Self::Int32),
80 6 => Ok(Self::Float32),
81 7 => Ok(Self::Float64),
82 _ => Err(TileError::new(
83 TileErrorCode::InvalidFieldValue,
84 format!("Unsupported packed dtype code {code}."),
85 )),
86 }
87 }
88
89 pub fn byte_size(self) -> usize {
90 match self {
91 Self::Uint8 | Self::Int8 => 1,
92 Self::Uint16 | Self::Int16 => 2,
93 Self::Uint32 | Self::Int32 | Self::Float32 => 4,
94 Self::Float64 => 8,
95 }
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
100pub enum CompressionMode {
101 #[default]
102 None,
103 DeflateRaw,
104}
105
106impl CompressionMode {
107 pub(crate) fn code(self) -> u8 {
108 match self {
109 Self::None => 0,
110 Self::DeflateRaw => 1,
111 }
112 }
113
114 pub(crate) fn from_code(code: u8) -> Result<Self> {
115 match code {
116 0 => Ok(Self::None),
117 1 => Ok(Self::DeflateRaw),
118 _ => Err(TileError::new(
119 TileErrorCode::InvalidFieldValue,
120 format!("Invalid compression code {code}."),
121 )),
122 }
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct TileDimensions {
128 pub rows: u32,
129 pub cols: u32,
130 pub bands: u8,
131}
132
133impl TileDimensions {
134 pub(crate) fn validate(self) -> Result<()> {
135 if self.rows == 0 || self.cols == 0 || self.bands == 0 {
136 return Err(TileError::new(
137 TileErrorCode::InvalidFieldValue,
138 "rows, cols, and bands must be > 0.",
139 ));
140 }
141 Ok(())
142 }
143
144 pub(crate) fn total_samples(self) -> Result<u64> {
145 let rows = u64::from(self.rows);
146 let cols = u64::from(self.cols);
147 let bands = u64::from(self.bands);
148 rows.checked_mul(cols)
149 .and_then(|v| v.checked_mul(bands))
150 .ok_or_else(|| {
151 TileError::new(
152 TileErrorCode::InvalidFieldValue,
153 "Invalid dimensions resulting in overflowed sample count.",
154 )
155 })
156 }
157}
158
159#[derive(Debug, Clone, PartialEq)]
160pub struct TileHeader {
161 pub format_major: u8,
162 pub tile_id: u64,
163 pub mesh_kind: MeshKind,
164 pub dtype: DType,
165 pub endianness: Endianness,
166 pub compression: CompressionMode,
167 pub dimensions: TileDimensions,
168 pub no_data_kind: u8,
169 pub no_data_value_raw: [u8; 8],
170 pub no_data: Option<f64>,
171 pub payload_uncompressed_bytes: u64,
172 pub payload_compressed_bytes: u64,
173 pub payload_crc32: u32,
174 pub header_crc32: u32,
175}
176
177#[derive(Debug, Clone)]
178pub struct TileEncodeInput<'a> {
179 pub tile_id: u64,
180 pub mesh_kind: MeshKind,
181 pub dtype: DType,
182 pub endianness: Endianness,
183 pub compression: CompressionMode,
184 pub dimensions: TileDimensions,
185 pub no_data: Option<f64>,
186 pub payload: &'a [u8],
187}
188
189#[derive(Debug, Clone, PartialEq)]
190pub struct EncodedTile {
191 pub bytes: Vec<u8>,
192 pub header: TileHeader,
193}
194
195#[derive(Debug, Clone, PartialEq)]
196pub struct DecodedTile {
197 pub header: TileHeader,
198 pub payload: Vec<u8>,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub enum TileErrorCode {
203 InvalidMagic,
204 UnsupportedVersion,
205 InvalidHeaderLength,
206 InvalidFieldValue,
207 MissingRequiredField,
208 HeaderChecksumMismatch,
209 InvalidPayloadLength,
210 UnsupportedCompression,
211 CompressionFailed,
212 DecompressionFailed,
213 PayloadChecksumMismatch,
214}
215
216impl TileErrorCode {
217 pub fn as_str(self) -> &'static str {
218 match self {
219 Self::InvalidMagic => "INVALID_MAGIC",
220 Self::UnsupportedVersion => "UNSUPPORTED_VERSION",
221 Self::InvalidHeaderLength => "INVALID_HEADER_LENGTH",
222 Self::InvalidFieldValue => "INVALID_FIELD_VALUE",
223 Self::MissingRequiredField => "MISSING_REQUIRED_FIELD",
224 Self::HeaderChecksumMismatch => "HEADER_CHECKSUM_MISMATCH",
225 Self::InvalidPayloadLength => "INVALID_PAYLOAD_LENGTH",
226 Self::UnsupportedCompression => "UNSUPPORTED_COMPRESSION",
227 Self::CompressionFailed => "COMPRESSION_FAILED",
228 Self::DecompressionFailed => "DECOMPRESSION_FAILED",
229 Self::PayloadChecksumMismatch => "PAYLOAD_CHECKSUM_MISMATCH",
230 }
231 }
232}
233
234impl fmt::Display for TileErrorCode {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 write!(f, "{}", self.as_str())
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct TileError {
242 pub code: TileErrorCode,
243 pub message: String,
244}
245
246impl TileError {
247 pub fn new(code: TileErrorCode, message: impl Into<String>) -> Self {
248 Self {
249 code,
250 message: message.into(),
251 }
252 }
253}
254
255impl fmt::Display for TileError {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "{}: {}", self.code, self.message)
258 }
259}
260
261impl std::error::Error for TileError {}
262
263pub type Result<T> = std::result::Result<T, TileError>;
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 fn tile_dims() -> TileDimensions {
270 TileDimensions {
271 rows: 2,
272 cols: 2,
273 bands: 1,
274 }
275 }
276
277 #[test]
278 fn roundtrip_uncompressed_payload() {
279 let payload =
280 encode_payload_values(DType::Uint16, Endianness::Little, &[10.0, 20.0, 30.0, 40.0])
281 .expect("encode payload values");
282
283 let encoded = encode_tile(TileEncodeInput {
284 tile_id: 42,
285 mesh_kind: MeshKind::JisX0410,
286 dtype: DType::Uint16,
287 endianness: Endianness::Little,
288 compression: CompressionMode::None,
289 dimensions: tile_dims(),
290 no_data: None,
291 payload: &payload,
292 })
293 .expect("encode tile");
294
295 let decoded = decode_tile_minimal(&encoded.bytes).expect("decode tile");
296 assert_eq!(decoded.header.tile_id, 42);
297 assert_eq!(decoded.header.compression, CompressionMode::None);
298 assert_eq!(decoded.payload, payload);
299
300 let values = decode_payload_values(
301 decoded.header.dtype,
302 decoded.header.endianness,
303 &decoded.payload,
304 decoded.header.no_data,
305 )
306 .expect("decode payload values");
307 assert_eq!(values, vec![Some(10.0), Some(20.0), Some(30.0), Some(40.0)]);
308 }
309
310 #[test]
311 fn roundtrip_deflate_payload() {
312 let payload =
313 encode_payload_values(DType::Uint16, Endianness::Little, &[1.0, 2.0, 3.0, 4.0])
314 .expect("encode payload values");
315
316 let encoded = encode_tile(TileEncodeInput {
317 tile_id: 1004,
318 mesh_kind: MeshKind::JisX0410,
319 dtype: DType::Uint16,
320 endianness: Endianness::Little,
321 compression: CompressionMode::DeflateRaw,
322 dimensions: tile_dims(),
323 no_data: None,
324 payload: &payload,
325 })
326 .expect("encode tile");
327
328 let decoded = decode_tile_minimal(&encoded.bytes).expect("decode tile");
329 assert_eq!(decoded.header.compression, CompressionMode::DeflateRaw);
330 assert_eq!(decoded.payload, payload);
331 }
332
333 #[test]
334 fn rejects_invalid_magic() {
335 let payload =
336 encode_payload_values(DType::Uint8, Endianness::Little, &[1.0, 2.0, 3.0, 4.0])
337 .expect("encode payload values");
338
339 let encoded = encode_tile(TileEncodeInput {
340 tile_id: 1,
341 mesh_kind: MeshKind::JisX0410,
342 dtype: DType::Uint8,
343 endianness: Endianness::Little,
344 compression: CompressionMode::None,
345 dimensions: tile_dims(),
346 no_data: None,
347 payload: &payload,
348 })
349 .expect("encode tile");
350
351 let mut malformed = encoded.bytes;
352 malformed[1] = 0;
353 let error = decode_tile_minimal(&malformed).expect_err("should fail");
354 assert_eq!(error.code, TileErrorCode::InvalidMagic);
355 }
356
357 #[test]
358 fn rejects_invalid_xyz_tile_id() {
359 let payload =
360 encode_payload_values(DType::Uint8, Endianness::Little, &[1.0, 2.0, 3.0, 4.0])
361 .expect("encode payload values");
362
363 let bad_tile_id = (1_u64 << 58) | 16_u64;
364 let error = encode_tile(TileEncodeInput {
365 tile_id: bad_tile_id,
366 mesh_kind: MeshKind::Xyz,
367 dtype: DType::Uint8,
368 endianness: Endianness::Little,
369 compression: CompressionMode::None,
370 dimensions: tile_dims(),
371 no_data: None,
372 payload: &payload,
373 })
374 .expect_err("should reject bad xyz tile id");
375
376 assert_eq!(error.code, TileErrorCode::InvalidFieldValue);
377 }
378
379 #[test]
380 fn decodes_no_data_samples_to_none() {
381 let payload =
382 encode_payload_values(DType::Uint16, Endianness::Little, &[10.0, 20.0, 30.0, 20.0])
383 .expect("encode payload values");
384
385 let encoded = encode_tile(TileEncodeInput {
386 tile_id: 2001,
387 mesh_kind: MeshKind::JisX0410,
388 dtype: DType::Uint16,
389 endianness: Endianness::Little,
390 compression: CompressionMode::None,
391 dimensions: tile_dims(),
392 no_data: Some(20.0),
393 payload: &payload,
394 })
395 .expect("encode tile");
396
397 let decoded = decode_tile_minimal(&encoded.bytes).expect("decode tile");
398 let values = decode_payload_values(
399 decoded.header.dtype,
400 decoded.header.endianness,
401 &decoded.payload,
402 decoded.header.no_data,
403 )
404 .expect("decode payload values");
405
406 assert_eq!(values, vec![Some(10.0), None, Some(30.0), None]);
407 }
408}