18std::int64_t days_from_civil(
int y,
unsigned m,
unsigned d) {
19 y -= (m <= 2) ? 1 : 0;
20 const std::int64_t era = (y >= 0 ? y : y - 399) / 400;
21 const unsigned yoe =
static_cast<unsigned>(y - era * 400);
22 const unsigned doy = (153 * (m + (m > 2 ?
static_cast<unsigned>(-3) : 9)) + 2) / 5 + d - 1;
23 const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
24 return era * 146097 +
static_cast<std::int64_t
>(doe) - 719468;
40Rfc3339Result parse_rfc3339(
const std::string& s) {
45 auto is_digit = [](
char c) {
return c >=
'0' && c <=
'9'; };
46 auto two = [&](std::size_t at) ->
int {
return (s[at] -
'0') * 10 + (s[at + 1] -
'0'); };
48 for (std::size_t i : {0u, 1u, 2u, 3u, 5u, 6u, 8u, 9u})
51 if (s[4] !=
'-' || s[7] !=
'-')
53 if (s[10] !=
'T' && s[10] !=
't' && s[10] !=
' ')
55 for (std::size_t i : {11u, 12u, 14u, 15u, 17u, 18u})
58 if (s[13] !=
':' || s[16] !=
':')
61 const int year = (s[0] -
'0') * 1000 + (s[1] -
'0') * 100 + (s[2] -
'0') * 10 + (s[3] -
'0');
62 const int month = two(5);
63 const int day = two(8);
64 const int hour = two(11);
65 const int minute = two(14);
66 const int second = two(17);
68 if (month < 1 || month > 12)
70 static const int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
71 const bool leap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
72 const int max_day = (month == 2 && leap) ? 29 : days_in_month[month - 1];
73 if (day < 1 || day > max_day)
75 if (hour > 23 || minute > 59 || second > 60)
79 std::uint32_t
nanos = 0;
80 if (pos < s.size() && s[pos] ==
'.') {
81 const std::size_t start = ++pos;
82 while (pos < s.size() && is_digit(s[pos]))
86 std::string frac = s.substr(start, pos - start);
91 nanos =
static_cast<std::uint32_t
>(std::stoul(frac));
96 std::int64_t offset_seconds = 0;
97 if (s[pos] ==
'Z' || s[pos] ==
'z') {
99 }
else if (s[pos] ==
'+' || s[pos] ==
'-') {
100 const bool neg = s[pos] ==
'-';
102 if (pos + 5 > s.size() || !is_digit(s[pos]) || !is_digit(s[pos + 1]) || s[pos + 2] !=
':' ||
103 !is_digit(s[pos + 3]) || !is_digit(s[pos + 4]))
105 const int oh = two(pos);
106 const int om = two(pos + 3);
107 if (oh > 23 || om > 59)
109 offset_seconds = (oh * 3600 + om * 60) * (neg ? -1 : 1);
117 const std::int64_t days =
118 days_from_civil(year,
static_cast<unsigned>(month),
static_cast<unsigned>(day));
124 const int second_for_epoch = (second == 60) ? 59 : second;
126 nanos += 1'000'000'000u;
128 r.seconds = days * 86400 + hour * 3600 + minute * 60 + second_for_epoch - offset_seconds;
133bool looks_like_rfc3339(
const std::string& s) {
return parse_rfc3339(s).ok; }
135std::optional<::ColumnType> guess_type(
const nlohmann::ordered_json& value) {
136 if (value.is_boolean())
137 return ::ColumnType::Bool;
138 if (value.is_number()) {
139 if (value.is_number_float())
140 return ::ColumnType::Double;
141 if (value.is_number_unsigned())
142 return ::ColumnType::ULong;
143 if (value.is_number_integer()) {
152 const std::int64_t i = value.get<std::int64_t>();
153 return i < 0 ? ::ColumnType::Long : ::ColumnType::ULong;
155 return ::ColumnType::ULong;
157 if (value.is_string())
158 return looks_like_rfc3339(value.get_ref<
const std::string&>()) ? ::ColumnType::DateTime
159 : ::ColumnType::String;
160 if (value.is_array() || value.is_object())
161 return ::ColumnType::Json;
165template <
typename T>
void put_le(std::vector<std::uint8_t>& out, std::size_t at, T v) {
166 using U =
typename std::make_unsigned<T>::type;
167 const U u =
static_cast<U
>(v);
168 for (std::size_t i = 0; i <
sizeof(T); ++i)
169 out[at + i] =
static_cast<std::uint8_t
>(u >> (8 * i));
172void put_f32(std::vector<std::uint8_t>& out, std::size_t at,
float f) {
174 std::memcpy(&bits, &f,
sizeof(bits));
175 put_le<std::uint32_t>(out, at, bits);
178void put_f64(std::vector<std::uint8_t>& out, std::size_t at,
double d) {
180 std::memcpy(&bits, &d,
sizeof(bits));
181 put_le<std::uint64_t>(out, at, bits);
190std::int64_t as_i64_or0(
const nlohmann::ordered_json& v) {
191 if (v.is_number_unsigned()) {
192 const std::uint64_t u = v.get<std::uint64_t>();
193 return u <= static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())
194 ?
static_cast<std::int64_t
>(u)
197 if (v.is_number_integer())
198 return v.get<std::int64_t>();
202std::uint64_t as_u64_or0(
const nlohmann::ordered_json& v) {
203 if (v.is_number_unsigned())
204 return v.get<std::uint64_t>();
205 if (v.is_number_integer()) {
206 const std::int64_t i = v.get<std::int64_t>();
207 return i >= 0 ?
static_cast<std::uint64_t
>(i) : 0;
212double as_f64_or0(
const nlohmann::ordered_json& v) {
return v.is_number() ? v.get<
double>() : 0.0; }
214bool as_bool_or_false(
const nlohmann::ordered_json& v) {
return v.is_boolean() && v.get<
bool>(); }
216std::string as_str_or_empty(
const nlohmann::ordered_json& v) {
217 return v.is_string() ? v.get<std::string>() : std::
string();
223 if (!attrs.is_object()) {
230 schema.insert_or_assign(
231 "json", std::make_pair(
static_cast<std::uint16_t
>(schema.size()), ::ColumnType::Json));
234 for (
const auto& [
key, val] : attrs.items()) {
235 if (schema.find(
key) != schema.end() || val.is_null())
237 if (
auto coltype = guess_type(val)) {
239 std::make_pair(
static_cast<std::uint16_t
>(schema.size()), *coltype));
244std::size_t
attr_size(::ColumnType coltype,
const nlohmann::ordered_json& colval) {
246 case ::ColumnType::Byte:
247 return sizeof(std::int8_t);
248 case ::ColumnType::UByte:
249 return sizeof(std::uint8_t);
250 case ::ColumnType::Bool:
251 return sizeof(std::uint8_t);
252 case ::ColumnType::Short:
253 return sizeof(std::int16_t);
254 case ::ColumnType::UShort:
255 return sizeof(std::uint16_t);
256 case ::ColumnType::Int:
257 return sizeof(std::int32_t);
258 case ::ColumnType::UInt:
259 return sizeof(std::uint32_t);
260 case ::ColumnType::Long:
261 return sizeof(std::int64_t);
262 case ::ColumnType::ULong:
263 return sizeof(std::uint64_t);
264 case ::ColumnType::Float:
265 return sizeof(float);
266 case ::ColumnType::Double:
267 return sizeof(double);
268 case ::ColumnType::String:
269 case ::ColumnType::DateTime:
270 return sizeof(std::uint32_t) + as_str_or_empty(colval).size();
271 case ::ColumnType::Json:
272 return sizeof(std::uint32_t) + colval.dump().size();
273 case ::ColumnType::Binary:
274 return sizeof(std::uint32_t) + as_str_or_empty(colval).size();
281 std::vector<std::uint8_t> out;
282 if (!attr.is_object() || attr.empty())
285 std::vector<std::pair<std::string, std::pair<std::uint16_t, ::ColumnType>>> sorted(
286 schema.begin(), schema.end());
287 std::sort(sorted.begin(), sorted.end(),
288 [](
const auto& a,
const auto& b) { return a.second.first < b.second.first; });
290 for (
const auto& [name, idx_type] : sorted) {
291 const auto [
index, coltype] = idx_type;
292 auto it = attr.find(name);
293 if (it == attr.end() || it->is_null())
295 const nlohmann::ordered_json& val = *it;
297 const std::size_t
offset = out.size();
298 const std::size_t size =
attr_size(coltype, val);
299 out.resize(
offset +
sizeof(std::uint16_t) + size, 0);
301 const std::size_t value_offset =
offset +
sizeof(std::uint16_t);
304 case ::ColumnType::Bool:
305 out[value_offset] = as_bool_or_false(val) ? 1 : 0;
307 case ::ColumnType::Int:
308 put_le<std::int32_t>(out, value_offset,
static_cast<std::int32_t
>(as_i64_or0(val)));
310 case ::ColumnType::UInt:
311 put_le<std::uint32_t>(out, value_offset,
312 static_cast<std::uint32_t
>(as_u64_or0(val)));
314 case ::ColumnType::Byte:
315 out[value_offset] =
static_cast<std::uint8_t
>(as_i64_or0(val));
317 case ::ColumnType::UByte:
318 out[value_offset] =
static_cast<std::uint8_t
>(as_u64_or0(val));
320 case ::ColumnType::Short:
321 put_le<std::int16_t>(out, value_offset,
static_cast<std::int16_t
>(as_i64_or0(val)));
323 case ::ColumnType::UShort:
324 put_le<std::uint16_t>(out, value_offset,
325 static_cast<std::uint16_t
>(as_u64_or0(val)));
327 case ::ColumnType::Long:
328 put_le<std::int64_t>(out, value_offset, as_i64_or0(val));
330 case ::ColumnType::ULong:
331 put_le<std::uint64_t>(out, value_offset, as_u64_or0(val));
333 case ::ColumnType::Float:
334 put_f32(out, value_offset,
static_cast<float>(as_f64_or0(val)));
336 case ::ColumnType::Double:
337 put_f64(out, value_offset, as_f64_or0(val));
339 case ::ColumnType::String:
340 case ::ColumnType::DateTime: {
341 const std::string s = as_str_or_empty(val);
342 put_le<std::uint32_t>(out, value_offset,
static_cast<std::uint32_t
>(s.size()));
343 std::memcpy(out.data() + value_offset +
sizeof(std::uint32_t), s.data(), s.size());
346 case ::ColumnType::Json: {
347 const std::string json_str = val.dump();
348 put_le<std::uint32_t>(out, value_offset,
349 static_cast<std::uint32_t
>(json_str.size()));
350 std::memcpy(out.data() + value_offset +
sizeof(std::uint32_t), json_str.data(),
354 case ::ColumnType::Binary: {
355 const std::string s = as_str_or_empty(val);
356 put_le<std::uint32_t>(out, value_offset,
static_cast<std::uint32_t
>(s.size()));
357 std::memcpy(out.data() + value_offset +
sizeof(std::uint32_t), s.data(), s.size());
365::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::Column>>>
367 std::vector<std::pair<std::string, std::pair<std::uint16_t, ::ColumnType>>> sorted(
368 schema.begin(), schema.end());
369 std::sort(sorted.begin(), sorted.end(),
370 [](
const auto& a,
const auto& b) { return a.second.first < b.second.first; });
372 std::vector<::flatbuffers::Offset<::Column>> columns;
373 columns.reserve(sorted.size());
374 for (
const auto& [name, idx_type] : sorted) {
375 auto name_off = fbb.CreateString(name);
376 columns.push_back(CreateColumn(fbb, idx_type.first, name_off, idx_type.second));
378 return fbb.CreateVector(columns);
381std::vector<AttributeIndexEntry>
383 const std::vector<std::string>& indexing_attr) {
384 std::vector<AttributeIndexEntry> out;
385 if (!attr.is_object() || attr.empty())
388 for (
const auto& name : indexing_attr) {
389 auto val_it = attr.find(name);
390 if (val_it == attr.end())
392 auto schema_it = schema.find(name);
393 if (schema_it == schema.end())
395 const auto [
index, coltype] = schema_it->second;
396 const nlohmann::ordered_json& val = *val_it;
399 case ::ColumnType::Bool:
402 case ::ColumnType::Int:
406 case ::ColumnType::UInt:
410 case ::ColumnType::Long:
413 case ::ColumnType::ULong:
416 case ::ColumnType::Float:
419 case ::ColumnType::Double:
422 case ::ColumnType::String:
426 case ::ColumnType::DateTime: {
427 const Rfc3339Result parsed = parse_rfc3339(as_str_or_empty(val));
429 parsed.ok ? parsed.nanos : 0)});
432 case ::ColumnType::Byte:
433 case ::ColumnType::UByte:
434 case ::ColumnType::Short:
435 case ::ColumnType::UShort:
436 case ::ColumnType::Json:
437 case ::ColumnType::Binary:
446std::vector<AttributeIndexEntry>
449 const std::vector<std::string>& indexing_attr) {
450 std::vector<AttributeIndexEntry> out;
451 auto co_it = city_feature.find(
"CityObjects");
452 if (co_it == city_feature.end() || !co_it->is_object())
455 std::vector<std::string> object_ids;
456 object_ids.reserve(co_it->size());
457 for (
const auto& [
id, unused] : co_it->items())
458 object_ids.push_back(
id);
459 std::sort(object_ids.begin(), object_ids.end());
461 for (
const auto&
id : object_ids) {
462 const nlohmann::ordered_json& co = co_it->at(
id);
463 auto attr_it = co.find(
"attributes");
464 if (attr_it == co.end() || attr_it->is_null())
467 out.insert(out.end(), entries.begin(), entries.end());
Every failure the library reports is one of these.
static KeyValue from_datetime(std::int64_t seconds, std::uint32_t nanos)
static KeyValue from_i64(std::int64_t v)
static KeyValue from_u32(std::uint32_t v)
static KeyValue from_f32(float v)
static KeyValue from_i32(std::int32_t v)
static KeyValue from_f64(double v)
static KeyValue from_string(KeyKind kind, const std::string &v)
static KeyValue from_u64(std::uint64_t v)
static KeyValue from_bool(bool v)
std::vector< std::uint8_t > encode_attributes_with_schema(const nlohmann::ordered_json &attr, const AttributeSchema &schema)
Encodes attr (a CityJSON attributes object) against schema: repeated [u16 LE column index][value] rec...
std::size_t attr_size(::ColumnType coltype, const nlohmann::ordered_json &colval)
Byte width one value of coltype occupies in the attribute blob, EXCLUDING the 2-byte column-index pre...
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::Column > > > to_columns(::flatbuffers::FlatBufferBuilder &fbb, const AttributeSchema &schema)
Builds the Column vector for Header.columns or CityObject.columns, in ascending column-index order.
void add_attributes(AttributeSchema &schema, const nlohmann::ordered_json &attrs)
Adds every member of a JSON object to schema, assigning each new, non-null name the next free column ...
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...
std::map< std::string, std::pair< std::uint16_t, ::ColumnType > > AttributeSchema
Attribute schema: name -> (column index, column type).
std::vector< AttributeIndexEntry > attribute_to_index_entries(const nlohmann::ordered_json &attr, const AttributeSchema &schema, const std::vector< std::string > &indexing_attr)
Extracts index entries for indexing_attr from one CityJSON attributes object.