Skip to main content

mesh_data_tile/
encoder.rs

1use std::io::Write;
2
3use crc32fast::hash as crc32;
4use flate2::write::DeflateEncoder;
5
6use crate::common::{
7    encode_no_data_field, expected_payload_length, pack_dtype_endian,
8    validate_tile_id_for_mesh_kind, write_numeric_value,
9};
10use crate::consts::{
11    HEADER_CHECKSUM_INPUT_LENGTH, HEADER_CHECKSUM_OFFSET, MAGIC, OFFSET_BANDS, OFFSET_COLS,
12    OFFSET_COMPRESSED_PAYLOAD_LENGTH, OFFSET_COMPRESSION, OFFSET_DTYPE_ENDIAN, OFFSET_FORMAT_MAJOR,
13    OFFSET_MESH_KIND, OFFSET_NO_DATA_KIND, OFFSET_NO_DATA_VALUE, OFFSET_PAYLOAD_CHECKSUM,
14    OFFSET_ROWS, OFFSET_TILE_ID, OFFSET_UNCOMPRESSED_PAYLOAD_LENGTH, TILE_FIXED_HEADER_LENGTH,
15    TILE_VERSION_MAJOR,
16};
17use crate::{
18    CompressionMode, DType, EncodedTile, Endianness, Result, TileEncodeInput, TileError,
19    TileErrorCode, TileHeader,
20};
21
22pub fn encode_tile(input: TileEncodeInput<'_>) -> Result<EncodedTile> {
23    input.dimensions.validate()?;
24    validate_tile_id_for_mesh_kind(input.tile_id, input.mesh_kind)?;
25
26    let expected_payload_len = expected_payload_length(input.dimensions, input.dtype)?;
27    if input.payload.len() != expected_payload_len {
28        return Err(TileError::new(
29            TileErrorCode::InvalidPayloadLength,
30            format!(
31                "Payload byte length mismatch. expected={expected_payload_len} got={}",
32                input.payload.len()
33            ),
34        ));
35    }
36
37    let payload_crc32 = crc32(input.payload);
38    let compressed_payload = compress_payload(input.compression, input.payload)?;
39    let compressed_payload_len = compressed_payload.len();
40
41    let (no_data_kind, no_data_value_raw) =
42        encode_no_data_field(input.no_data, input.dtype, input.endianness)?;
43
44    let mut header_bytes = [0_u8; TILE_FIXED_HEADER_LENGTH];
45    header_bytes[0..4].copy_from_slice(&MAGIC);
46    header_bytes[OFFSET_FORMAT_MAJOR] = TILE_VERSION_MAJOR;
47    header_bytes[OFFSET_TILE_ID..OFFSET_TILE_ID + 8].copy_from_slice(&input.tile_id.to_le_bytes());
48    header_bytes[OFFSET_MESH_KIND] = input.mesh_kind.code();
49    header_bytes[OFFSET_DTYPE_ENDIAN] = pack_dtype_endian(input.dtype, input.endianness);
50    header_bytes[OFFSET_COMPRESSION] = input.compression.code();
51    header_bytes[OFFSET_ROWS..OFFSET_ROWS + 4]
52        .copy_from_slice(&input.dimensions.rows.to_le_bytes());
53    header_bytes[OFFSET_COLS..OFFSET_COLS + 4]
54        .copy_from_slice(&input.dimensions.cols.to_le_bytes());
55    header_bytes[OFFSET_BANDS] = input.dimensions.bands;
56    header_bytes[OFFSET_NO_DATA_KIND] = no_data_kind;
57    header_bytes[OFFSET_NO_DATA_VALUE..OFFSET_NO_DATA_VALUE + 8]
58        .copy_from_slice(&no_data_value_raw);
59    header_bytes[OFFSET_UNCOMPRESSED_PAYLOAD_LENGTH..OFFSET_UNCOMPRESSED_PAYLOAD_LENGTH + 8]
60        .copy_from_slice(&(input.payload.len() as u64).to_le_bytes());
61    header_bytes[OFFSET_COMPRESSED_PAYLOAD_LENGTH..OFFSET_COMPRESSED_PAYLOAD_LENGTH + 8]
62        .copy_from_slice(&(compressed_payload_len as u64).to_le_bytes());
63    header_bytes[OFFSET_PAYLOAD_CHECKSUM..OFFSET_PAYLOAD_CHECKSUM + 4]
64        .copy_from_slice(&payload_crc32.to_le_bytes());
65    header_bytes[HEADER_CHECKSUM_OFFSET..HEADER_CHECKSUM_OFFSET + 4]
66        .copy_from_slice(&0_u32.to_le_bytes());
67
68    let header_crc32 = crc32(&header_bytes[..HEADER_CHECKSUM_INPUT_LENGTH]);
69    header_bytes[HEADER_CHECKSUM_OFFSET..HEADER_CHECKSUM_OFFSET + 4]
70        .copy_from_slice(&header_crc32.to_le_bytes());
71
72    let mut bytes = Vec::with_capacity(TILE_FIXED_HEADER_LENGTH + compressed_payload_len);
73    bytes.extend_from_slice(&header_bytes);
74    bytes.extend_from_slice(&compressed_payload);
75
76    let header = TileHeader {
77        format_major: TILE_VERSION_MAJOR,
78        tile_id: input.tile_id,
79        mesh_kind: input.mesh_kind,
80        dtype: input.dtype,
81        endianness: input.endianness,
82        compression: input.compression,
83        dimensions: input.dimensions,
84        no_data_kind,
85        no_data_value_raw,
86        no_data: input.no_data,
87        payload_uncompressed_bytes: input.payload.len() as u64,
88        payload_compressed_bytes: compressed_payload_len as u64,
89        payload_crc32,
90        header_crc32,
91    };
92
93    Ok(EncodedTile { bytes, header })
94}
95
96pub fn encode_payload_values(
97    dtype: DType,
98    endianness: Endianness,
99    values: &[f64],
100) -> Result<Vec<u8>> {
101    let value_size = dtype.byte_size();
102    let mut out = vec![0_u8; values.len() * value_size];
103
104    for (idx, value) in values.iter().enumerate() {
105        let start = idx * value_size;
106        let end = start + value_size;
107        write_numeric_value(dtype, endianness, *value, true, &mut out[start..end])?;
108    }
109
110    Ok(out)
111}
112
113fn compress_payload(mode: CompressionMode, payload: &[u8]) -> Result<Vec<u8>> {
114    match mode {
115        CompressionMode::None => Ok(payload.to_vec()),
116        CompressionMode::DeflateRaw => {
117            let mut encoder = DeflateEncoder::new(Vec::new(), flate2::Compression::default());
118            encoder.write_all(payload).map_err(|err| {
119                TileError::new(
120                    TileErrorCode::CompressionFailed,
121                    format!("Could not compress payload using deflate-raw: {err}"),
122                )
123            })?;
124            encoder.finish().map_err(|err| {
125                TileError::new(
126                    TileErrorCode::CompressionFailed,
127                    format!("Could not finish deflate-raw compression: {err}"),
128                )
129            })
130        }
131    }
132}