11# define FCB_FSEEK _fseeki64
14# include <sys/types.h>
15# define FCB_FSEEK fseeko
24constexpr std::uint8_t kMagicBytes[8] = {
'f',
'c',
'b',
kVersion,
'f',
'c',
'b', 0};
29constexpr std::size_t kCopyChunkSize = 1 << 16;
35 std::optional<AttributeSchema> semantic_attr_schema)
36 : cj_(std::move(cj)), options_(std::move(options)), attr_schema_(std::move(attr_schema)),
37 semantic_attr_schema_(std::move(semantic_attr_schema)) {
39 indexing_attr_.push_back(name);
41 const auto& transform = cj_.at(
"transform");
42 scale_x_ = transform.at(
"scale").at(0).get<
double>();
43 scale_y_ = transform.at(
"scale").at(1).get<
double>();
44 translate_x_ = transform.at(
"translate").at(0).get<
double>();
45 translate_y_ = transform.at(
"translate").at(1).get<
double>();
47 tmp_ = std::tmpfile();
48 if (tmp_ ==
nullptr) {
55 if (tmp_ !=
nullptr) {
65 flatbuffers::FlatBufferBuilder fbb;
66 auto [off, raw_bbox] =
68 semantic_attr_schema_ ? &*semantic_attr_schema_ :
nullptr);
69 fbb.FinishSizePrefixed(off);
70 const std::uint64_t size = fbb.GetSize();
74 std::fwrite(fbb.GetBufferPointer(), 1, size, tmp_) != size) {
79 const std::uint64_t temp_id = feat_offsets_.size();
80 feat_offsets_.push_back(FeatureSlot{tmp_write_pos_, size});
81 tmp_write_pos_ += size;
83 feat_nodes_.push_back(
NodeItem{raw_bbox.
min_x * scale_x_ + translate_x_,
84 raw_bbox.min_y * scale_y_ + translate_y_,
85 raw_bbox.max_x * scale_x_ + translate_x_,
86 raw_bbox.max_y * scale_y_ + translate_y_, temp_id});
88 if (!indexing_attr_.empty()) {
89 index_entries_by_feature_.push_back(
92 index_entries_by_feature_.emplace_back();
115 const bool build_rtree = effective_node_size > 0 && !feat_nodes_.empty();
126 std::vector<std::uint64_t> final_offset_by_temp_id(feat_offsets_.size());
128 std::uint64_t running = 0;
129 for (
const auto& node : feat_nodes_) {
130 final_offset_by_temp_id[
static_cast<std::size_t
>(node.offset)] = running;
131 running += feat_offsets_[
static_cast<std::size_t
>(node.offset)].size;
135 std::vector<std::uint8_t> rtree_bytes;
140 std::vector<NodeItem> rtree_input = feat_nodes_;
141 for (
auto& node : rtree_input)
142 node.offset = final_offset_by_temp_id[
static_cast<std::size_t
>(node.offset)];
143 std::vector<NodeItem> tree =
build_packed_rtree(rtree_input, extent, effective_node_size);
154 std::vector<std::pair<std::string, std::optional<std::uint16_t>>> sorted_indices =
157 sorted_indices.begin(), sorted_indices.end(), [
this](
const auto& a,
const auto& b) {
158 const auto ia = attr_schema_.find(a.first);
159 const auto ib = attr_schema_.find(b.first);
160 const std::uint16_t idx_a = ia != attr_schema_.end()
162 : std::numeric_limits<std::uint16_t>::max();
163 const std::uint16_t idx_b = ib != attr_schema_.end()
165 : std::numeric_limits<std::uint16_t>::max();
166 return idx_a < idx_b;
169 std::vector<std::uint8_t> attr_index_bytes;
170 std::vector<AttributeIndexInfo> attr_index_info;
171 if (!sorted_indices.empty()) {
172 std::vector<std::vector<BtreeEntry>> entries_by_column(attr_schema_.size());
173 for (std::size_t temp_id = 0; temp_id < feat_offsets_.size(); ++temp_id) {
174 const std::uint64_t feature_offset = final_offset_by_temp_id[temp_id];
175 for (
const auto& e : index_entries_by_feature_[temp_id])
176 entries_by_column.at(e.index).push_back(
BtreeEntry{e.value, feature_offset});
179 for (
const auto& [name, bf_opt] : sorted_indices) {
180 const auto it = attr_schema_.find(name);
181 if (it == attr_schema_.end())
183 const std::uint16_t schema_index = it->second.first;
184 const auto& col_entries = entries_by_column.at(schema_index);
185 if (col_entries.empty())
192 attr_index_info.push_back(
195 attr_index_bytes.insert(attr_index_bytes.end(), built.
bytes.begin(), built.
bytes.end());
204 flatbuffers::FlatBufferBuilder header_fbb;
205 auto header_off =
to_fcb_header(header_fbb, cj_, header_options, attr_schema_,
206 semantic_attr_schema_ ? &*semantic_attr_schema_ :
nullptr,
207 attr_index_info.empty() ? nullptr : &attr_index_info);
208 header_fbb.FinishSizePrefixed(header_off);
210 out.write(
reinterpret_cast<const char*
>(kMagicBytes),
sizeof(kMagicBytes));
211 out.write(
reinterpret_cast<const char*
>(header_fbb.GetBufferPointer()),
212 static_cast<std::streamsize
>(header_fbb.GetSize()));
213 if (!rtree_bytes.empty())
214 out.write(
reinterpret_cast<const char*
>(rtree_bytes.data()),
215 static_cast<std::streamsize
>(rtree_bytes.size()));
216 if (!attr_index_bytes.empty())
217 out.write(
reinterpret_cast<const char*
>(attr_index_bytes.data()),
218 static_cast<std::streamsize
>(attr_index_bytes.size()));
227 std::vector<char> chunk(kCopyChunkSize);
228 for (
const auto& node : feat_nodes_) {
229 const FeatureSlot& slot = feat_offsets_[
static_cast<std::size_t
>(node.offset)];
235 std::uint64_t remaining = slot.size;
236 while (remaining > 0) {
237 const std::size_t want =
238 static_cast<std::size_t
>(std::min<std::uint64_t>(remaining, chunk.size()));
239 if (std::fread(chunk.data(), 1, want, tmp_) != want) {
241 "FcbWriter: failed reading a feature back from the temp file");
243 out.write(chunk.data(),
static_cast<std::streamsize
>(want));
246 "FcbWriter: failed writing a feature to the output");
254 std::ostringstream oss(std::ios::binary);
256 const std::string& s = oss.str();
257 return std::vector<std::uint8_t>(s.begin(), s.end());
Every failure the library reports is one of these.
void add_feature(const nlohmann::ordered_json &city_json_feature)
Encodes and spools one CityJSONFeature line.
std::vector< std::uint8_t > write()
Convenience wrapper around write(std::ostream&) that returns the complete file as one buffer.
FcbWriter(nlohmann::ordered_json cj, FcbWriterOptions options, AttributeSchema attr_schema, std::optional< AttributeSchema > semantic_attr_schema)
cj is the CityJSONSeq's metadata line (first line: type/ version/transform/metadata/etc).
void hilbert_sort(std::vector< NodeItem > &items, const NodeItem &extent)
Sorts items in place by descending Hilbert index (the item furthest along the curve first) – a STABLE...
std::vector< std::uint8_t > encode_packed_rtree(const std::vector< NodeItem > &tree)
Serializes every node in tree (as returned by build_packed_rtree) in array order, 40 bytes each.
constexpr std::uint16_t kDefaultBranchingFactor
Mirrors static_btree::DEFAULT_BRANCHING_FACTOR (static_btree/mod.rs:19), used whenever a caller reque...
BuiltBtreeIndex build_static_btree(const std::vector< BtreeEntry > &entries, KeyKind kind, std::uint16_t branching_factor)
Builds one column's complete attribute index blob from its (key, offset) entries.
KeyKind key_kind_for_column(std::uint8_t column_type)
Column type to key kind, following what the WRITER emits.
std::vector< NodeItem > build_packed_rtree(const std::vector< NodeItem > &nodes, const NodeItem &extent, std::uint16_t node_size)
Builds the full flat packed-R-tree node array (leaves first in nodes's own order at the array's tail ...
std::vector< AttributeIndexEntry > cityfeature_to_index_entries(const nlohmann::ordered_json &city_feature, const AttributeSchema &schema, const std::vector< std::string > &indexing_attr)
Same, over every object in one CityJSONFeature's CityObjects, visited in ascending object-id order (n...
KeyKind
The concrete key types the B+tree index can hold.
NodeItem calc_extent(const std::vector< NodeItem > &nodes)
The bbox union of every item, via repeated NodeItem::expand starting from NodeItem::empty(0).
constexpr std::uint8_t kVersion
std::map< std::string, std::pair< std::uint16_t, ::ColumnType > > AttributeSchema
Attribute schema: name -> (column index, column type).
std::pair<::flatbuffers::Offset<::CityFeature >, NodeItem > to_fcb_city_feature(::flatbuffers::FlatBufferBuilder &fbb, const std::string &id, const nlohmann::ordered_json &city_feature, const AttributeSchema &attr_schema, const AttributeSchema *semantic_attr_schema)
Builds one CityFeature table – its CityObjects (visited in ascending id order), vertices,...
::flatbuffers::Offset<::Header > to_fcb_header(::flatbuffers::FlatBufferBuilder &fbb, const nlohmann::ordered_json &cj, const HeaderWriterOptions &options, const AttributeSchema &attr_schema, const AttributeSchema *semantic_attr_schema, const std::vector< AttributeIndexInfo > *attribute_indices_info)
Builds the whole Header table from the CityJSON metadata line (the first line of a CityJSONSeq: type/...
One attribute column's B+tree index metadata, as recorded in the header (AttributeIndex,...
One (key, feature byte offset) pair to be indexed.
The finished index: the flat node array concatenated with the payload section (mirrors Stree::stream_...
std::vector< std::uint8_t > bytes
std::uint32_t num_unique_items
std::uint16_t branching_factor
Configuration for FcbWriter.
bool write_index
false forces index_node_size to 0 in the written header (no R-tree at all), regardless of index_node_...
std::uint16_t index_node_size
std::vector< std::pair< std::string, std::optional< std::uint16_t > > > attribute_indices
(attribute name, branching factor); std::nullopt branching factor means build_static_btree's own defa...
std::optional< std::array< double, 6 > > geographical_extent
One R-tree node entry: 4 doubles then a u64, all little-endian, 40 bytes with no padding (packed_rtre...
static NodeItem empty(std::uint64_t offset)
The "empty" node used as the fold/aggregation identity: any real bbox's expand widens it.