Skip to main content

Crate fcb_core

Crate fcb_core 

Source
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 CityFeature table — 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

FlagDefaultEffect
httpyesHttpFcbReader 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

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_readerhttp
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§

CityJSONSeq
FcbReader
FcbWriter
Main writer for FlatCityBuf (FCB) format
FeatureIter
Float
A wrapper around floats providing implementations of Eq, Ord, and Hash.
Meta
MetaColumn

Enums§

CJType
CJTypeKind
MetaColumnType

Constants§

HEADER_MAX_BUFFER_SIZE
HEADER_SIZE_SIZE
MAGIC_BYTES
MAGIC_BYTES_SIZE
VERSION

Traits§

CityJSONReader

Functions§

add_indices_to_multi_memory_index
add_indices_to_multi_stream_index
build_query
check_magic_bytes
Returns true if bytes starts 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)

Type Aliases§

AttrQuery