2#include <fcb/generated/feature_generated.h>
3#include <fcb/generated/header_generated.h>
22Feature::Feature(std::shared_ptr<
const std::vector<std::uint8_t>> buffer, std::uint64_t byte_offset,
23 std::size_t body_offset)
24 : buffer_(std::move(buffer)), byte_offset_(byte_offset), body_offset_(body_offset) {}
27 if (buffer_ ==
nullptr)
29 return GetSizePrefixedCityFeature(buffer_->data() + body_offset_);
35 const ::CityFeature* cf = raw();
36 if (cf ==
nullptr || cf->id() ==
nullptr)
38 return cf->id()->str();
42const ::CityObject* object_at(const ::CityFeature* cf, std::size_t i) {
43 if (cf ==
nullptr || cf->objects() ==
nullptr)
45 if (i >= cf->objects()->size())
47 return cf->objects()->Get(
static_cast<flatbuffers::uoffset_t
>(i));
52 const auto* obj = object_at(raw(), i);
53 if (obj ==
nullptr || obj->attributes() ==
nullptr)
55 const auto* a = obj->attributes();
60 const auto* obj = object_at(raw(), i);
61 return obj !=
nullptr && obj->attributes() !=
nullptr;
65 const auto* obj = object_at(raw(), i);
66 if (obj ==
nullptr || obj->geographical_extent() ==
nullptr)
68 const auto* e = obj->geographical_extent();
71 auto rd = [](
const void* base, std::size_t off) {
73 std::memcpy(&d,
static_cast<const std::uint8_t*
>(base) + off,
sizeof(
double));
76 out = {rd(e, 0), rd(e, 8), rd(e, 16), rd(e, 24), rd(e, 32), rd(e, 40)};
81 const auto* obj = object_at(raw(), i);
82 return obj !=
nullptr && obj->columns() !=
nullptr;
86 std::vector<ColumnInfo> out;
87 const auto* obj = object_at(raw(), i);
88 if (obj ==
nullptr || obj->columns() ==
nullptr)
90 out.reserve(obj->columns()->size());
91 for (
const auto* c : *obj->columns()) {
95 ci.
index = c->index();
96 ci.name = c->name() !=
nullptr ? c->name()->str() : std::string();
97 ci.type =
static_cast<std::uint8_t
>(c->type());
98 ci.nullable = c->nullable();
99 out.push_back(std::move(ci));
105 const auto* obj = object_at(raw(), i);
106 if (obj ==
nullptr || obj->id() ==
nullptr)
108 return obj->id()->str();
112 const ::CityFeature* cf = raw();
113 if (cf ==
nullptr || cf->objects() ==
nullptr)
115 return cf->objects()->size();
122 : reader_(std::move(
reader)), header_(std::move(header)), mode_(mode), hits_(std::move(hits)) {
128 const std::uint64_t total_size = reader_->total_size();
130 std::uint64_t at = 0;
140 if (cursor_ < total_size) {
142 "trailing bytes after " + std::to_string(
features_count) +
" features");
146 if (!known && cursor_ >= total_size) {
152 if (hit_index_ >= hits_.size()) {
163 if (at >= total_size) {
167 auto prefix = reader_->read(at, 4);
168 if (prefix.size() < 4) {
179 std::to_string(produced_));
182 const std::uint32_t len =
static_cast<std::uint32_t
>(prefix[0]) |
183 (
static_cast<std::uint32_t
>(prefix[1]) << 8) |
184 (
static_cast<std::uint32_t
>(prefix[2]) << 16) |
185 (
static_cast<std::uint32_t
>(prefix[3]) << 24);
191 "implausible feature size: " + std::to_string(len));
196 auto raw_buf = reader_->read(at, want);
197 if (raw_buf.size() < want) {
201 auto buf = std::make_shared<std::vector<std::uint8_t>>(
kBodyAlignPad + raw_buf.size());
202 std::copy(raw_buf.begin(), raw_buf.end(), buf->begin() +
kBodyAlignPad);
206 if (!VerifySizePrefixedCityFeatureBuffer(verifier)) {
208 "feature failed FlatBuffers verification at offset " + std::to_string(at));
211 current_ =
Feature(std::const_pointer_cast<
const std::vector<std::uint8_t>>(buf),
224 : reader_(std::move(
reader)), header_(std::move(header)) {}
227 return open(std::make_shared<FileRangeReader>(path));
236 const auto& info = header_.
info();
237 const auto& layout = header_.
layout();
239 if (layout.rtree_size == 0 || info.features_count == 0) {
246 auto index_reader = std::make_shared<BufferedRangeReader>(reader_, 256 * 1024);
247 auto hits =
rtree_search_bbox(*index_reader, layout.rtree_begin, info.features_count,
248 info.index_node_size, query);
250 auto feature_reader = std::make_shared<BufferedRangeReader>(reader_, 1048576);
278 const std::string& a = v.
s;
280 const int c = a.compare(b);
349 auto index_reader = std::make_shared<BufferedRangeReader>(reader_, 1024 * 1024);
351 std::vector<SearchResultItem> acc;
353 bool any_post_filter =
false;
355 for (
const auto& cond : query) {
359 if (c.name == cond.field) {
364 if (col ==
nullptr) {
379 if (col->
type ==
static_cast<std::uint8_t
>(::ColumnType::Json) ||
380 col->
type ==
static_cast<std::uint8_t
>(::ColumnType::Binary)) {
382 "column " + cond.field +
383 " is Json/Binary: its index is a fixed-width key "
384 "over a blob, so hits are meaningless without "
385 "post-verification");
390 if (a.column_index == col->
index) {
395 if (idx ==
nullptr) {
400 if (needs_post_filter(
kind))
401 any_post_filter =
true;
403 auto hits =
stree_query(*index_reader, *idx,
kind, cond.op, cond.value);
405 std::sort(hits.begin(), hits.end(),
407 return a.offset < b.offset;
409 hits.erase(std::unique(hits.begin(), hits.end(),
411 return a.offset == b.offset;
416 acc = std::move(hits);
420 std::vector<SearchResultItem> merged;
421 std::set_intersection(acc.begin(), acc.end(), hits.begin(), hits.end(),
422 std::back_inserter(merged),
424 return a.offset < b.offset;
426 acc = std::move(merged);
432 auto feature_reader = std::make_shared<BufferedRangeReader>(reader_, 1048576);
443 std::vector<SearchResultItem> verified;
445 while (probe.
next()) {
448 for (
const auto& cond : query) {
451 if (c.name == cond.field) {
456 if (col ==
nullptr) {
461 if (!needs_post_filter(
kind))
466 bool matched =
false;
474 if (name != cond.field)
476 if (value_satisfies(val, cond.op, cond.value,
kind))
491 auto out_reader = std::make_shared<BufferedRangeReader>(reader_, 1048576);
493 std::move(verified));
500 auto buffered = std::make_shared<BufferedRangeReader>(reader_, 1048576);
Every failure the library reports is one of these.
The library's entry point.
FeatureIterator select_bbox(const BBox &query)
Iterate features whose 2D bounding box intersects query.
static FcbReader open(std::shared_ptr< RangeReader > reader)
FeatureIterator select_all()
Iterate every feature in stored (Hilbert) order.
const HeaderView & header() const
FeatureIterator select_attr(const AttrQuery &query, AttrQueryOptions opts={})
Iterate features matching every condition (AND).
static FcbReader open_file(const std::string &path)
Single-pass iterator over features. Not copyable.
std::uint64_t features_count() const
Total features the header claims, for progress reporting.
const Feature & current() const
FeatureIterator(std::shared_ptr< RangeReader > reader, HeaderView header, IterationMode mode, std::vector< SearchResultItem > hits)
One decoded feature that OWNS the bytes it points into.
bool object_extent(std::size_t i, std::array< double, 6 > &out) const
CityObject i's own bounding box, if it declares one.
bool object_has_attributes(std::size_t i) const
Whether CityObject i declares an attributes vector at all.
bytes_view object_attributes(std::size_t i) const
Raw attribute blob of CityObject i, or empty if it has none.
const ::CityFeature * raw() const
The generated CityFeature table behind this Feature.
std::uint64_t byte_offset() const
Byte offset of this feature RELATIVE to the start of the features section, matching the offsets store...
std::string object_id(std::size_t i) const
CityObject i's id.
bool object_has_columns(std::size_t i) const
CityObject i's own column schema, if it declares one.
std::string id() const
The feature's CityJSON id. Empty when empty().
std::vector< ColumnInfo > object_columns(std::size_t i) const
std::size_t city_object_count() const
How many CityObjects this feature carries. Zero when empty().
static KeyValue from_i64(std::int64_t v)
const std::string & original_string() const
The original, untruncated string this key was built from.
static KeyValue from_f64(double v)
static KeyValue from_u64(std::uint64_t v)
static KeyValue from_bool(bool v)
Minimal C++17 stand-in for std::span: a non-owning view over contiguous memory.
void require_within(std::uint64_t offset, std::uint64_t length, std::uint64_t limit, const char *what)
Throws unless [offset, offset+length) lies wholly within limit.
std::uint64_t checked_add(std::uint64_t a, std::uint64_t b, const char *what="add")
constexpr std::uint64_t kMaxFeatureSize
Hard ceiling on a single feature's byte length, enforced before allocating.
span< const std::uint8_t > bytes_view
The workhorse alias: a read-only view over bytes.
std::vector< SearchResultItem > rtree_search_bbox(RangeReader &reader, std::uint64_t index_begin, std::uint64_t num_items, std::uint16_t node_size, const BBox &query)
Breadth-first bbox search over the packed R-tree, reading nodes through the supplied reader.
std::vector< SearchResultItem > stree_query(RangeReader &reader, const AttrIndexInfo &index, KeyKind kind, Operator op, const KeyValue &value)
Run one condition against one column's index blob, returning candidate feature offsets (relative to t...
IterationMode
How a FeatureIterator decides what to visit.
@ SequentialScan
walk the features section start to finish
@ OffsetList
visit exactly the offsets supplied (possibly none)
KeyKind key_kind_for_column(std::uint8_t column_type)
Column type to key kind, following what the WRITER emits.
HeaderView read_header(std::shared_ptr< RangeReader > reader)
Read and validate the file preamble and header.
int compare_keys(const KeyValue &a, const KeyValue &b)
Three-way comparison.
KeyKind
The concrete key types the B+tree index can hold.
std::vector< AttrCondition > AttrQuery
std::vector< std::pair< std::string, AttrValue > > decode_attributes(bytes_view blob, const std::vector< ColumnInfo > &schema)
Decode a feature's attribute blob against the column schema.
Operator
Comparison operators the attribute index supports.
static constexpr std::size_t kBodyAlignPad
No padding needed.
Where one column's B+tree index lives, and how it is shaped.
bool exact_index_only
Return raw index candidates without verifying them against the decoded attribute.
One decoded attribute value.
One attribute column's schema, copied out of the header.
std::uint64_t features_count
std::vector< ColumnInfo > columns
std::uint64_t feature_begin
One hit from an index traversal.
static const ::CityFeature * get(const Feature &f)