Expand description
FlatCityBuf (.fcb) — a cloud-optimized binary encoding of
CityJSON. It carries the standard’s semantics in FlatBuffers, laid out
so that a client can read only the bytes it actually needs.
A file is five contiguous sections:
| magic bytes | header | packed Hilbert | static B+tree | features |
| 8 bytes | | R-tree (opt.) | indices (opt.)| |- the header is one FlatBuffers table: transform (scale/translate for the quantized integer vertices), CRS, geographical extent, appearance, geometry templates, and the attribute column schema;
- the packed Hilbert R-tree answers bbox and point queries without a scan. Features are stored in Hilbert order, so a hit list is a set of sorted, coalescible byte ranges;
- the static B+tree indices answer attribute queries (
==,!=,<,<=,>,>=) over the columns chosen at write time; - each feature is a size-prefixed
CityFeaturetable — one per line of the source CityJSONSeq.
Because the layout is seek-friendly, the same reader works over a local
file (Read + Seek), a non-seekable stream (Read), or a remote URL via
HTTP range requests (HttpFcbReader, http feature, enabled by default).
fcb_core is the reference implementation of the format and the only one
that writes it; the C++, Python and TypeScript readers in the same
repository are validated against its output.
§Reading a file
FcbReader::open parses and verifies the header; select_* then returns
a fallible streaming iterator over the features. Only one feature is held
in memory at a time.
use fcb_core::{deserializer::to_cj_metadata, FcbReader};
use std::fs::File;
use std::io::BufReader;
let file = BufReader::new(File::open("delft.fcb")?);
let mut features = FcbReader::open(file)?.select_all()?;
// The CityJSON metadata object (version, transform, CRS, extent) is the
// header; it is the first line of the equivalent CityJSONSeq document.
let cj = to_cj_metadata(&features.header())?;
println!("CityJSON {}, {} features", cj.version, features.header().features_count());
while let Some(feature) = features.next()? {
let cj_feature = feature.cur_cj_feature()?;
println!("{}: {} city object(s)", cj_feature.id, cj_feature.city_objects.len());
}§Spatial and attribute queries
FcbReader::select_query uses the R-tree; FcbReader::select_attr_query
uses the B+tree indices. Both skip straight to the matching features.
use fcb_core::{AttrQuery, FcbReader, KeyType, Operator, SpatialQuery};
use std::fs::File;
use std::io::BufReader;
// Everything inside a bounding box (min_x, min_y, max_x, max_y).
let file = BufReader::new(File::open("delft.fcb")?);
let bbox = SpatialQuery::BBox(84_000.0, 446_000.0, 85_000.0, 447_000.0);
let mut hits = FcbReader::open(file)?.select_query(bbox, None, None)?;
while let Some(feature) = hits.next()? {
println!("{}", feature.cur_cj_feature()?.id);
}
// Everything whose indexed `b3_h_dak_50p` attribute exceeds 2.0.
let file = BufReader::new(File::open("delft.fcb")?);
let query: AttrQuery = vec![(
"b3_h_dak_50p".to_string(),
Operator::Gt,
KeyType::Float64(2.0.into()),
)];
let mut hits = FcbReader::open(file)?.select_attr_query(query)?;
while let Some(feature) = hits.next()? {
println!("{}", feature.cur_cj_feature()?.id);
}§Writing a file
FcbWriter takes the CityJSON metadata object plus a stream of
CityJSONFeatures and assembles header, indices and feature data on
FcbWriter::write.
use fcb_core::{
attribute::{AttributeSchema, AttributeSchemaMethods},
header_writer::HeaderWriterOptions,
read_cityjson_from_reader, CJType, CJTypeKind, CityJSONSeq, FcbWriter,
};
use std::fs::File;
use std::io::{BufReader, BufWriter};
let input = BufReader::new(File::open("delft.city.jsonl")?);
let CJType::Seq(CityJSONSeq { cj, features }) =
read_cityjson_from_reader(input, CJTypeKind::Seq)?
else {
unreachable!("CJTypeKind::Seq always yields CJType::Seq")
};
// Collect the attribute columns. Iterate the city objects in a
// deterministic order: `add_attributes` hands each new name the next free
// column index, so a `HashMap`'s random order would number the columns
// differently on every run.
let mut schema = AttributeSchema::new();
for feature in &features {
let mut ids: Vec<&String> = feature.city_objects.keys().collect();
ids.sort_unstable();
for co in ids.into_iter().filter_map(|id| feature.city_objects.get(id)) {
if let Some(attributes) = &co.attributes {
schema.add_attributes(attributes);
}
}
}
let options = HeaderWriterOptions {
write_index: true,
feature_count: features.len() as u64,
index_node_size: 16,
// Build a static B+tree over these columns. `None` = default
// branching factor.
attribute_indices: Some(vec![("b3_h_dak_50p".to_string(), None)]),
geographical_extent: None,
};
let mut fcb = FcbWriter::new(cj, Some(options), Some(schema), None)?;
for feature in &features {
fcb.add_feature(feature)?;
}
fcb.write(BufWriter::new(File::create("delft.fcb")?))?;§Reading over HTTP
HttpFcbReader fetches the header, then the index, then only the byte
ranges holding the matching features — typically a handful of range
requests for a query against a multi-gigabyte file.
use fcb_core::{HttpFcbReader, SpatialQuery};
let reader = HttpFcbReader::open("https://example.com/delft.fcb").await?;
let bbox = SpatialQuery::BBox(84_000.0, 446_000.0, 85_000.0, 447_000.0);
let mut features = reader.select_query(bbox).await?;
while features.next().await?.is_some() {
println!("{}", features.cur_cj_feature()?.id);
}§Feature flags
| Flag | Default | Effect |
|---|---|---|
http | yes | HttpFcbReader and the range-request query paths, via reqwest and http-range-client. Disable it (default-features = false) for a dependency-light, purely local reader. |
http_reader is additionally gated on not(target_arch = "wasm32")
because it reaches for reqwest’s native client. The rest of the crate,
including the index search paths, still compiles for wasm32.
§Attribution
Portions of this software are derived from FlatGeobuf
- Source: https://github.com/flatgeobuf/flatgeobuf
- License: BSD 2-Clause License
- Copyright (c) 2018-2024, Björn Harrtell and contributors
Specifically, the following components contain code derived from FlatGeobuf:
- Spatial indexing algorithms (packed R-tree implementation)
- HTTP range request handling (for Rust native part)
- Binary format design patterns
We extend our gratitude to the FlatGeobuf team for their excellent work on efficient geospatial binary formats, which provided the foundation for FlatCityBuf’s spatial indexing and serialization architecture.
§License
This project is licensed under the MIT License. FlatGeobuf portions remain under their original BSD 2-Clause License.
Re-exports§
pub use error::Error;pub use packed_rtree::NodeItem;pub use packed_rtree::PackedRTree;pub use packed_rtree::Query as SpatialQuery;pub use packed_rtree::SearchResultItem;pub use static_btree::Entry;pub use static_btree::FixedStringKey;pub use static_btree::Key;pub use static_btree::KeyType;pub use static_btree::MemoryIndex;pub use static_btree::MemoryMultiIndex;pub use static_btree::MultiIndex;pub use static_btree::Operator;pub use static_btree::Query;pub use static_btree::QueryCondition;pub use static_btree::StreamIndex;pub use static_btree::StreamMultiIndex;pub use fb::*;pub use http_reader::*;http
Modules§
- attribute
- city_
buffer - deserializer
- error
- fb
- feature_
writer - geom_
decoder - Rebuilding a CityJSON geometry from the flat arrays FlatCityBuf stores.
- geom_
encoder - Flattening of a CityJSON geometry into the arrays FlatCityBuf stores.
- header_
writer - http_
reader http - HTTP reader for FlatCityBuf files
- obj
- A minimal CityJSON-to-OBJ writer.
- packed_
rtree - Create and read a packed Hilbert R-Tree to enable fast bounding box spatial filtering.
- serializer
- static_
btree
Structs§
- CityJSON
Seq - FcbReader
- FcbWriter
- Main writer for FlatCityBuf (FCB) format
- Feature
Iter - Float
- A wrapper around floats providing implementations of
Eq,Ord, andHash. - Meta
- Meta
Column
Enums§
Constants§
Traits§
Functions§
- add_
indices_ to_ multi_ memory_ index - add_
indices_ to_ multi_ stream_ index - build_
query - check_
magic_ bytes - Returns
trueifbytesstarts with a FlatCityBuf magic-byte sequence this build can read. - read_
cityjson - Read CityJSON from a file path
- read_
cityjson_ from_ reader - Read CityJSON from any reader (file or stdin)