FlatCityBuf C++ reader 0.8.0
Native C++17 reader for FlatCityBuf, the cloud-optimized CityJSON format
Loading...
Searching...
No Matches
attribute.cpp
Go to the documentation of this file.
1#include <fcb/attribute.hpp>
2#include <fcb/generated/header_generated.h>
3
4#include <algorithm>
5#include <cstring>
6#include <unordered_map>
7
8#include "detail/checked.hpp"
9
10namespace fcb {
11
12namespace {
13
14template <typename T> T get_le(bytes_view b, std::size_t at) {
15 using U = typename std::make_unsigned<T>::type;
16 U u = 0;
17 for (std::size_t i = 0; i < sizeof(T); ++i) {
18 u |= static_cast<U>(b[at + i]) << (8 * i);
19 }
20 return static_cast<T>(u);
21}
22
23float get_f32(bytes_view b, std::size_t at) {
24 const std::uint32_t bits = get_le<std::uint32_t>(b, at);
25 float f;
26 std::memcpy(&f, &bits, sizeof(f));
27 return f;
28}
29
30double get_f64(bytes_view b, std::size_t at) {
31 const std::uint64_t bits = get_le<std::uint64_t>(b, at);
32 double d;
33 std::memcpy(&d, &bits, sizeof(d));
34 return d;
35}
36
37void need(bytes_view b, std::size_t at, std::size_t n, const char* what) {
38 if (at > b.size() || b.size() - at < n) {
40 std::string("truncated attribute blob reading ") + what);
41 }
42}
43
44} // namespace
45
46std::vector<std::pair<std::string, AttrValue>>
47decode_attributes(bytes_view blob, const std::vector<ColumnInfo>& schema) {
48 std::vector<std::pair<std::string, AttrValue>> out;
49 if (blob.empty())
50 return out;
51
52 std::unordered_map<std::uint16_t, const ColumnInfo*> by_index;
53 by_index.reserve(schema.size());
54 for (const auto& c : schema)
55 by_index.emplace(c.index, &c);
56
57 std::size_t at = 0;
58 while (at < blob.size()) {
59 need(blob, at, 2, "column index");
60 const std::uint16_t col_index = get_le<std::uint16_t>(blob, at);
61 at += 2;
62
63 auto found = by_index.find(col_index);
64 if (found == by_index.end()) {
66 "attribute references unknown column index " + std::to_string(col_index));
67 }
68 const ColumnInfo& col = *found->second;
69 const auto type = static_cast<::ColumnType>(col.type);
70
71 AttrValue v{};
72 switch (type) {
73 case ::ColumnType::Bool:
74 need(blob, at, 1, "Bool");
75 v.type = AttrValue::Type::Bool;
76 v.b = blob[at] != 0;
77 at += 1;
78 break;
79 case ::ColumnType::Short:
80 need(blob, at, 2, "Short");
81 v.type = AttrValue::Type::Int;
82 v.i = get_le<std::int16_t>(blob, at);
83 at += 2;
84 break;
85 case ::ColumnType::UShort:
86 need(blob, at, 2, "UShort");
87 v.type = AttrValue::Type::UInt;
88 v.u = get_le<std::uint16_t>(blob, at);
89 at += 2;
90 break;
91 case ::ColumnType::Int:
92 need(blob, at, 4, "Int");
93 v.type = AttrValue::Type::Int;
94 v.i = get_le<std::int32_t>(blob, at);
95 at += 4;
96 break;
97 case ::ColumnType::UInt:
98 need(blob, at, 4, "UInt");
99 v.type = AttrValue::Type::UInt;
100 v.u = get_le<std::uint32_t>(blob, at);
101 at += 4;
102 break;
103 case ::ColumnType::Long:
104 need(blob, at, 8, "Long");
105 v.type = AttrValue::Type::Int;
106 v.i = get_le<std::int64_t>(blob, at);
107 at += 8;
108 break;
109 case ::ColumnType::ULong:
110 need(blob, at, 8, "ULong");
111 v.type = AttrValue::Type::UInt;
112 v.u = get_le<std::uint64_t>(blob, at);
113 at += 8;
114 break;
115 case ::ColumnType::Float:
116 need(blob, at, 4, "Float");
118 v.d = static_cast<double>(get_f32(blob, at));
119 at += 4;
120 break;
121 case ::ColumnType::Double:
122 need(blob, at, 8, "Double");
124 v.d = get_f64(blob, at);
125 at += 8;
126 break;
127
128 case ::ColumnType::String:
129 case ::ColumnType::DateTime:
130 case ::ColumnType::Json: {
131 // u32 LE byte length, then UTF-8. Note DateTime is a STRING
132 // here, unlike its 12-byte packed form as a B+tree key.
133 need(blob, at, 4, "string length");
134 const std::uint32_t len = get_le<std::uint32_t>(blob, at);
135 at += 4;
136 need(blob, at, len, "string body");
137 v.type =
138 (type == ::ColumnType::Json) ? AttrValue::Type::Json : AttrValue::Type::String;
139 v.s.assign(reinterpret_cast<const char*>(blob.data()) + at, len);
140 at += len;
141 break;
142 }
143
144 // Byte is UNSIGNED on the wire: the writer stores it as a raw u8
145 // (writer/attribute.rs) and indexes it as u8 (writer/attr_index.rs),
146 // so a stored 200 must read back as 200, not -56. The Rust reader
147 // agrees on both its value and its index path.
148 case ::ColumnType::Byte:
149 need(blob, at, 1, "Byte");
150 v.type = AttrValue::Type::UInt;
151 v.u = get_le<std::uint8_t>(blob, at);
152 at += 1;
153 break;
154 case ::ColumnType::UByte:
155 need(blob, at, 1, "UByte");
156 v.type = AttrValue::Type::UInt;
157 v.u = get_le<std::uint8_t>(blob, at);
158 at += 1;
159 break;
160
161 case ::ColumnType::Binary: {
162 // u32 LE byte length, then that many raw bytes -- the same
163 // framing as String, but the payload is not text.
164 need(blob, at, 4, "binary length");
165 const std::uint32_t len = get_le<std::uint32_t>(blob, at);
166 at += 4;
167 need(blob, at, len, "binary body");
169 v.s.assign(reinterpret_cast<const char*>(blob.data()) + at, len);
170 at += len;
171 break;
172 }
173 }
174
175 out.emplace_back(col.name, std::move(v));
176 }
177 return out;
178}
179
180#ifdef FCB_WITH_JSON
181nlohmann::json attributes_to_json(bytes_view blob, const std::vector<ColumnInfo>& schema) {
182 nlohmann::json obj = nlohmann::json::object();
183 for (auto& [name, v] : decode_attributes(blob, schema)) {
184 switch (v.type) {
186 obj[name] = nullptr;
187 break;
189 obj[name] = v.b;
190 break;
192 obj[name] = v.i;
193 break;
195 obj[name] = v.u;
196 break;
198 obj[name] = v.d;
199 break;
201 obj[name] = v.s;
202 break;
204 // Raw bytes have no faithful JSON form; emit as a byte array.
205 obj[name] = std::vector<std::uint8_t>(v.s.begin(), v.s.end());
206 break;
208 // Stored as text; re-parse so it nests as real JSON, which is
209 // what the Rust deserializer does (serde_json::from_str).
210 obj[name] = nlohmann::json::parse(v.s, nullptr, /*allow_exceptions=*/false);
211 break;
212 }
213 }
214 return obj;
215}
216#endif
217
218} // namespace fcb
Every failure the library reports is one of these.
Definition error.hpp:30
Minimal C++17 stand-in for std::span: a non-owning view over contiguous memory.
Definition span.hpp:13
bool empty() const noexcept
Definition span.hpp:27
std::size_t size() const noexcept
Definition span.hpp:26
T * data() const noexcept
Definition span.hpp:25
span< const std::uint8_t > bytes_view
The workhorse alias: a read-only view over bytes.
Definition span.hpp:43
nlohmann::json attributes_to_json(bytes_view blob, const std::vector< ColumnInfo > &schema)
Same decode, rendered as a JSON object for CityJSON emission.
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.
Definition attribute.cpp:47
bool found
Definition stree.cpp:102
One decoded attribute value.
Definition attribute.hpp:23
One attribute column's schema, copied out of the header.
Definition header.hpp:27
std::uint8_t type
Definition header.hpp:30
std::string name
Definition header.hpp:29