Skip to main content

fcb_core/writer/
header_writer.rs

1use crate::error::Result;
2use crate::packed_rtree::PackedRTree;
3use crate::serializer::to_fcb_header;
4use cjseq::CityJSON;
5use flatbuffers::FlatBufferBuilder;
6
7use super::{attribute::AttributeSchema, serializer::AttributeIndexInfo};
8
9/// Writer for converting CityJSON header information to FlatBuffers format
10pub struct HeaderWriter<'a> {
11    /// FlatBuffers builder instance
12    pub fbb: FlatBufferBuilder<'a>,
13    /// Source CityJSON data
14    pub cj: CityJSON,
15
16    /// Configuration options for header writing
17    pub header_options: HeaderWriterOptions,
18    /// Attribute schema
19    pub attr_schema: AttributeSchema,
20
21    /// Semantic attribute schema
22    pub semantic_attr_schema: Option<AttributeSchema>,
23    /// Attribute indices
24    pub(super) attribute_indices_info: Option<Vec<AttributeIndexInfo>>,
25}
26
27/// Configuration options for header writing process
28#[derive(Debug, Clone)]
29pub struct HeaderWriterOptions {
30    /// Whether to write index information
31    pub write_index: bool,
32    pub feature_count: u64,
33    /// Size of the index node
34    pub index_node_size: u16,
35    /// Attribute indices
36    pub attribute_indices: Option<Vec<(String, Option<u16>)>>, // (field name, branching factor)
37    /// Geographical extent
38    pub geographical_extent: Option<[f64; 6]>,
39}
40
41impl Default for HeaderWriterOptions {
42    fn default() -> Self {
43        HeaderWriterOptions {
44            write_index: true,
45            index_node_size: PackedRTree::DEFAULT_NODE_SIZE,
46            feature_count: 0,
47            attribute_indices: None,
48            geographical_extent: None,
49        }
50    }
51}
52
53impl<'a> HeaderWriter<'a> {
54    /// Creates a new HeaderWriter with optional configuration
55    ///
56    /// # Arguments
57    ///
58    /// * `cj` - The CityJSON data to write
59    /// * `header_options` - Optional configuration for the header writing process
60    pub(super) fn new(
61        cj: CityJSON,
62        header_options: Option<HeaderWriterOptions>,
63        attr_schema: AttributeSchema,
64        semantic_attr_schema: Option<AttributeSchema>,
65    ) -> HeaderWriter<'a> {
66        Self::new_with_options(
67            header_options.unwrap_or_default(),
68            cj,
69            attr_schema,
70            semantic_attr_schema,
71        )
72    }
73
74    /// Creates a new HeaderWriter with specific configuration
75    ///
76    /// # Arguments
77    ///
78    /// * `options` - Configuration for the header writing process
79    /// * `cj` - The CityJSON data to write
80    fn new_with_options(
81        mut options: HeaderWriterOptions,
82        cj: CityJSON,
83        attr_schema: AttributeSchema,
84        semantic_attr_schema: Option<AttributeSchema>,
85    ) -> HeaderWriter<'a> {
86        let fbb = FlatBufferBuilder::new();
87        // `index_node_size` is the caller's; only `write_index: false` may
88        // override it, and then only to 0, which is how the header says "no
89        // R-tree". Forcing DEFAULT_NODE_SIZE here made the field write-only
90        // and every file's node size 16, so no reader could be tested
91        // against a non-default one.
92        if !options.write_index {
93            options.index_node_size = 0;
94        }
95        HeaderWriter {
96            fbb,
97            cj,
98            header_options: options,
99            attr_schema,
100            semantic_attr_schema,
101            attribute_indices_info: None,
102        }
103    }
104
105    /// Finalizes the header and returns it as a byte vector
106    ///
107    /// # Returns
108    ///
109    /// A size-prefixed FlatBuffer containing the serialized header
110    pub(super) fn finish_to_header(mut self) -> Result<Vec<u8>> {
111        let header = to_fcb_header(
112            &mut self.fbb,
113            &self.cj,
114            self.header_options,
115            &self.attr_schema,
116            self.semantic_attr_schema.as_ref(),
117            self.attribute_indices_info
118                .as_ref()
119                .filter(|info| !info.is_empty())
120                .map(|info| info.as_slice()),
121        )?;
122        self.fbb.finish_size_prefixed(header, None);
123        Ok(self.fbb.finished_data().to_vec())
124    }
125}