FlatCityBuf C++ reader 0.8.0
Native C++17 reader for FlatCityBuf, the cloud-optimized CityJSON format
Loading...
Searching...
No Matches
feature_serializer.cpp
Go to the documentation of this file.
2
3#ifdef FCB_WITH_JSON
4
5# include <fcb/error.hpp>
6
7# include <algorithm>
8
9namespace fcb {
10
11namespace {
12
13// Same 33 strings, in the same enum order, as cityjson.cpp's
14// kCityObjectTypeNames -- kept separate rather than shared, mirroring
15// Rust's own split between deserializer.rs's to_cj_co_type (read) and
16// serializer.rs's to_co_type (write).
17const char* const kCityObjectTypeNames[] = {
18 "Bridge",
19 "BridgePart",
20 "BridgeInstallation",
21 "BridgeConstructiveElement",
22 "BridgeRoom",
23 "BridgeFurniture",
24 "Building",
25 "BuildingPart",
26 "BuildingInstallation",
27 "BuildingConstructiveElement",
28 "BuildingFurniture",
29 "BuildingStorey",
30 "BuildingRoom",
31 "BuildingUnit",
32 "CityFurniture",
33 "CityObjectGroup",
34 "GenericCityObject",
35 "LandUse",
36 "OtherConstruction",
37 "PlantCover",
38 "SolitaryVegetationObject",
39 "TINRelief",
40 "Road",
41 "Railway",
42 "Waterway",
43 "TransportSquare",
44 "Tunnel",
45 "TunnelPart",
46 "TunnelInstallation",
47 "TunnelConstructiveElement",
48 "TunnelHollowSpace",
49 "TunnelFurniture",
50 "WaterBody",
51};
52constexpr std::size_t kCityObjectTypeCount =
53 sizeof(kCityObjectTypeNames) / sizeof(kCityObjectTypeNames[0]);
54
55const char* const kSemanticSurfaceTypeNames[] = {
56 "RoofSurface", "GroundSurface", "WallSurface", "ClosureSurface",
57 "OuterCeilingSurface", "OuterFloorSurface", "Window", "Door",
58 "InteriorWallSurface", "CeilingSurface", "FloorSurface", "WaterSurface",
59 "WaterGroundSurface", "WaterClosureSurface", "TrafficArea", "AuxiliaryTrafficArea",
60 "TransportationMarking", "TransportationHole",
61};
62constexpr std::size_t kSemanticSurfaceTypeCount =
63 sizeof(kSemanticSurfaceTypeNames) / sizeof(kSemanticSurfaceTypeNames[0]);
64
65std::optional<::flatbuffers::Offset<::flatbuffers::Vector<double>>>
66to_color(::flatbuffers::FlatBufferBuilder& fbb, const nlohmann::ordered_json& obj,
67 const char* key) {
68 auto it = obj.find(key);
69 if (it == obj.end() || it->is_null())
70 return std::nullopt;
71 std::vector<double> v;
72 for (const auto& c : *it)
73 v.push_back(c.get<double>());
74 return fbb.CreateVector(v);
75}
76
83::flatbuffers::Offset<::SemanticObject>
84to_semantic_object(::flatbuffers::FlatBufferBuilder& fbb, const nlohmann::ordered_json& surface,
85 const AttributeSchema* semantic_attr_schema) {
86 auto [type_, extension_type_name] =
87 semantic_surface_type_from_name(surface.at("type").get<std::string>());
88
89 // Builder call order matches Rust's `to_geometry` semantic-object arm
90 // (writer/serializer.rs:920-949) exactly: `children` is created BEFORE
91 // `extension_type`. FlatBuffers builder calls are side-effecting (each
92 // appends to the buffer), so this order is part of the wire format, not
93 // just source-code style -- it must be sequenced with separate named
94 // locals, never left to argument-evaluation order.
95 std::optional<::flatbuffers::Offset<::flatbuffers::Vector<std::uint32_t>>> children;
96 if (auto it = surface.find("children"); it != surface.end() && it->is_array()) {
97 std::vector<std::uint32_t> c;
98 for (const auto& v : *it)
99 c.push_back(v.get<std::uint32_t>());
100 children = fbb.CreateVector(c);
101 }
102
103 auto extension_type =
104 extension_type_name ? std::optional(fbb.CreateString(*extension_type_name)) : std::nullopt;
105
106 ::flatbuffers::Optional<std::uint32_t> parent = ::flatbuffers::nullopt;
107 if (auto it = surface.find("parent"); it != surface.end() && !it->is_null())
108 parent = it->get<std::uint32_t>();
109
110 nlohmann::ordered_json other = nlohmann::ordered_json::object();
111 for (const auto& [key, val] : surface.items())
112 if (key != "type" && key != "parent" && key != "children")
113 other[key] = val;
114
115 std::optional<::flatbuffers::Offset<::flatbuffers::Vector<std::uint8_t>>> attributes;
116 if (!other.empty() && semantic_attr_schema != nullptr) {
117 attributes = fbb.CreateVector(encode_attributes_with_schema(other, *semantic_attr_schema));
118 }
119
120 return CreateSemanticObject(fbb, type_, attributes.value_or(0), children.value_or(0), parent,
121 extension_type.value_or(0));
122}
123
124} // namespace
125
126CoType city_object_type_from_name(const std::string& name) {
127 for (std::size_t i = 0; i < kCityObjectTypeCount; ++i) {
128 if (name == kCityObjectTypeNames[i])
129 return {static_cast<::CityObjectType>(i), std::nullopt};
130 }
131 return {::CityObjectType::ExtensionObject, name};
132}
133
135 for (std::size_t i = 0; i < kSemanticSurfaceTypeCount; ++i) {
136 if (name == kSemanticSurfaceTypeNames[i])
137 return {static_cast<::SemanticSurfaceType>(i), std::nullopt};
138 }
139 return {::SemanticSurfaceType::ExtraSemanticSurface, name};
140}
141
142::flatbuffers::Offset<::Appearance> to_appearance(::flatbuffers::FlatBufferBuilder& fbb,
143 const nlohmann::ordered_json& appearance) {
144 std::optional<::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::Material>>>>
145 materials_off;
146 if (auto it = appearance.find("materials"); it != appearance.end() && it->is_array()) {
147 std::vector<::flatbuffers::Offset<::Material>> materials;
148 for (const auto& m : *it) {
149 // Builder call order matches Rust's `to_appearance` material arm
150 // (writer/serializer.rs:542-566) exactly: name, then
151 // diffuse/emissive/specular color IN THAT ORDER, each a
152 // separate sequenced statement -- C++ does not specify
153 // function-argument evaluation order, so these side-effecting
154 // `fbb.CreateVector`/`CreateString` calls must never be written
155 // as inline call arguments.
156 auto name = fbb.CreateString(m.at("name").get<std::string>());
157 auto diffuse_color = to_color(fbb, m, "diffuseColor");
158 auto emissive_color = to_color(fbb, m, "emissiveColor");
159 auto specular_color = to_color(fbb, m, "specularColor");
160
161 auto ambient = m.find("ambientIntensity");
162 auto shininess = m.find("shininess");
163 auto transparency = m.find("transparency");
164 auto is_smooth = m.find("isSmooth");
165 materials.push_back(CreateMaterial(
166 fbb, name,
167 ambient != m.end() && !ambient->is_null()
168 ? ::flatbuffers::Optional<double>(ambient->get<double>())
169 : ::flatbuffers::nullopt,
170 diffuse_color.value_or(0), emissive_color.value_or(0), specular_color.value_or(0),
171 shininess != m.end() && !shininess->is_null()
172 ? ::flatbuffers::Optional<double>(shininess->get<double>())
173 : ::flatbuffers::nullopt,
174 transparency != m.end() && !transparency->is_null()
175 ? ::flatbuffers::Optional<double>(transparency->get<double>())
176 : ::flatbuffers::nullopt,
177 is_smooth != m.end() && !is_smooth->is_null()
178 ? ::flatbuffers::Optional<bool>(is_smooth->get<bool>())
179 : ::flatbuffers::nullopt));
180 }
181 materials_off = fbb.CreateVector(materials);
182 }
183
184 std::optional<::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::Texture>>>>
185 textures_off;
186 if (auto it = appearance.find("textures"); it != appearance.end() && it->is_array()) {
187 std::vector<::flatbuffers::Offset<::Texture>> textures;
188 for (const auto& t : *it) {
189 // `type` maps `Some(PNG)|absent -> PNG`; only "JPG" (any case
190 // handled by exact string match) selects the other tag.
191 auto type_it = t.find("type");
192 const ::TextureFormat format = (type_it != t.end() && *type_it == "JPG")
193 ? ::TextureFormat::JPG
194 : ::TextureFormat::PNG;
195 auto image = fbb.CreateString(t.value("image", std::string()));
196
197 ::flatbuffers::Optional<::WrapMode> wrap_mode = ::flatbuffers::nullopt;
198 if (auto w = t.find("wrapMode"); w != t.end() && !w->is_null()) {
199 const std::string& s = w->get_ref<const std::string&>();
200 if (s == "none")
201 wrap_mode = ::WrapMode::None;
202 else if (s == "wrap")
203 wrap_mode = ::WrapMode::Wrap;
204 else if (s == "mirror")
205 wrap_mode = ::WrapMode::Mirror;
206 else if (s == "clamp")
207 wrap_mode = ::WrapMode::Clamp;
208 else if (s == "border")
209 wrap_mode = ::WrapMode::Border;
210 }
211
212 ::flatbuffers::Optional<::TextureType> texture_type = ::flatbuffers::nullopt;
213 if (auto tt = t.find("textureType"); tt != t.end() && !tt->is_null()) {
214 const std::string& s = tt->get_ref<const std::string&>();
215 if (s == "unknown")
216 texture_type = ::TextureType::Unknown;
217 else if (s == "specific")
218 texture_type = ::TextureType::Specific;
219 else if (s == "typical")
220 texture_type = ::TextureType::Typical;
221 }
222
223 // Builder call order matches Rust's texture arm
224 // (writer/serializer.rs:579-599): `image` (already a separate
225 // statement above), then `border_color`, sequenced explicitly
226 // for the same reason as the material loop above.
227 auto border_color = to_color(fbb, t, "borderColor");
228 textures.push_back(CreateTexture(fbb, format, image, wrap_mode, texture_type,
229 border_color.value_or(0)));
230 }
231 textures_off = fbb.CreateVector(textures);
232 }
233
234 std::optional<::flatbuffers::Offset<::flatbuffers::Vector<const ::Vec2*>>> vertices_texture_off;
235 if (auto it = appearance.find("vertices-texture"); it != appearance.end() && it->is_array()) {
236 std::vector<::Vec2> uvs;
237 for (const auto& v : *it)
238 uvs.push_back(::Vec2(v.at(0).get<double>(), v.at(1).get<double>()));
239 vertices_texture_off = fbb.CreateVectorOfStructs(uvs);
240 }
241
242 std::optional<::flatbuffers::Offset<::flatbuffers::String>> default_theme_texture_off;
243 if (auto it = appearance.find("default-theme-texture");
244 it != appearance.end() && it->is_string())
245 default_theme_texture_off = fbb.CreateString(it->get<std::string>());
246
247 std::optional<::flatbuffers::Offset<::flatbuffers::String>> default_theme_material_off;
248 if (auto it = appearance.find("default-theme-material");
249 it != appearance.end() && it->is_string())
250 default_theme_material_off = fbb.CreateString(it->get<std::string>());
251
252 return CreateAppearance(fbb, materials_off.value_or(0), textures_off.value_or(0),
253 vertices_texture_off.value_or(0), default_theme_texture_off.value_or(0),
254 default_theme_material_off.value_or(0));
255}
256
257::flatbuffers::Offset<::Geometry> to_geometry(::flatbuffers::FlatBufferBuilder& fbb,
258 const nlohmann::ordered_json& geometry,
259 const AttributeSchema* semantic_attr_schema) {
260 const GeometryKind kind = geometry_kind_from_name(geometry.at("type").get<std::string>());
261 const auto type_ = static_cast<::GeometryType>(kind);
262 auto lod = geometry.find("lod");
263 auto lod_off = (lod != geometry.end() && lod->is_string())
264 ? std::optional(fbb.CreateString(lod->get<std::string>()))
265 : std::nullopt;
266
267 EncodedGeometry encoded = encode(geometry);
268 auto solids_off = fbb.CreateVector(encoded.boundaries.solids);
269 auto shells_off = fbb.CreateVector(encoded.boundaries.shells);
270 auto surfaces_off = fbb.CreateVector(encoded.boundaries.surfaces);
271 auto strings_off = fbb.CreateVector(encoded.boundaries.strings);
272 auto boundaries_off = fbb.CreateVector(encoded.boundaries.indices);
273
274 std::optional<::flatbuffers::Offset<::flatbuffers::Vector<std::uint32_t>>> semantics_values_off;
275 std::optional<
276 ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::SemanticObject>>>>
277 semantics_objects_off;
278 if (encoded.semantics) {
279 std::vector<::flatbuffers::Offset<::SemanticObject>> objects;
280 for (const auto& surface : encoded.semantics->surfaces)
281 objects.push_back(to_semantic_object(fbb, surface, semantic_attr_schema));
282 semantics_objects_off = fbb.CreateVector(objects);
283 if (encoded.semantics->values)
284 semantics_values_off = fbb.CreateVector(*encoded.semantics->values);
285 }
286
287 std::optional<
288 ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::MaterialMapping>>>>
289 material_off;
290 if (encoded.materials) {
291 std::vector<::flatbuffers::Offset<::MaterialMapping>> mappings;
292 for (const auto& m : *encoded.materials) {
293 auto theme = fbb.CreateString(m.theme);
294 switch (m.kind) {
296 mappings.push_back(CreateMaterialMapping(
297 fbb, theme, 0, 0, 0, ::flatbuffers::Optional<std::uint32_t>(m.value)));
298 break;
300 // Present-but-empty: created unconditionally, even when
301 // a level genuinely has zero entries, so `[]` stays
302 // distinct from an absent field. Order (solids, shells,
303 // vertices) matches Rust's `GMMaterialMapping::Values`
304 // arm (writer/serializer.rs:966-978) exactly, as three
305 // separately sequenced statements -- see the
306 // to_semantic_object note on why this cannot be inline
307 // call arguments.
308 auto solids = fbb.CreateVector(m.solids);
309 auto shells = fbb.CreateVector(m.shells);
310 auto vertices = fbb.CreateVector(m.vertices);
311 mappings.push_back(CreateMaterialMapping(fbb, theme, solids, shells, vertices,
312 ::flatbuffers::nullopt));
313 break;
314 }
316 mappings.push_back(
317 CreateMaterialMapping(fbb, theme, 0, 0, 0, ::flatbuffers::nullopt));
318 break;
319 }
320 }
321 material_off = fbb.CreateVector(mappings);
322 }
323
324 std::optional<
325 ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::TextureMapping>>>>
326 texture_off;
327 if (encoded.textures) {
328 std::vector<::flatbuffers::Offset<::TextureMapping>> mappings;
329 for (const auto& t : *encoded.textures) {
330 auto theme = fbb.CreateString(t.theme);
331 if (t.has_values) {
332 // As with material Values: all five arrays are created
333 // unconditionally, even where empty. Order (solids, shells,
334 // surfaces, strings, vertices) matches Rust's texture arm
335 // (writer/serializer.rs:1022-1029) exactly, as five
336 // separately sequenced statements.
337 auto solids = fbb.CreateVector(t.solids);
338 auto shells = fbb.CreateVector(t.shells);
339 auto surfaces = fbb.CreateVector(t.surfaces);
340 auto strings = fbb.CreateVector(t.strings);
341 auto vertices = fbb.CreateVector(t.vertices);
342 mappings.push_back(
343 CreateTextureMapping(fbb, theme, solids, shells, surfaces, strings, vertices));
344 } else {
345 mappings.push_back(CreateTextureMapping(fbb, theme, 0, 0, 0, 0, 0));
346 }
347 }
348 texture_off = fbb.CreateVector(mappings);
349 }
350
351 return CreateGeometry(fbb, type_, lod_off.value_or(0), solids_off, shells_off, surfaces_off,
352 strings_off, boundaries_off, semantics_values_off.value_or(0),
353 semantics_objects_off.value_or(0), material_off.value_or(0),
354 texture_off.value_or(0));
355}
356
357::flatbuffers::Offset<::GeometryInstance>
358to_geometry_instance(::flatbuffers::FlatBufferBuilder& fbb,
359 const nlohmann::ordered_json& geometry) {
360 if (geometry.at("type").get<std::string>() != "GeometryInstance") {
362 "to_geometry_instance called on a non-GeometryInstance geometry");
363 }
364
365 const std::uint32_t template_ = geometry.at("template").get<std::uint32_t>();
366
367 std::vector<std::uint32_t> indices;
368 for (const auto& v : geometry.at("boundaries"))
369 indices.push_back(v.get<std::uint32_t>());
370 auto boundaries_off = fbb.CreateVector(indices);
371
372 const auto& m = geometry.at("transformationMatrix");
373 ::TransformationMatrix matrix(
374 m.at(0).get<double>(), m.at(1).get<double>(), m.at(2).get<double>(), m.at(3).get<double>(),
375 m.at(4).get<double>(), m.at(5).get<double>(), m.at(6).get<double>(), m.at(7).get<double>(),
376 m.at(8).get<double>(), m.at(9).get<double>(), m.at(10).get<double>(),
377 m.at(11).get<double>(), m.at(12).get<double>(), m.at(13).get<double>(),
378 m.at(14).get<double>(), m.at(15).get<double>());
379
380 return CreateGeometryInstance(fbb, &matrix, template_, boundaries_off);
381}
382
383::GeographicalExtent to_geographical_extent(const std::array<double, 6>& extent) {
384 return ::GeographicalExtent(::Vector(extent[0], extent[1], extent[2]),
385 ::Vector(extent[3], extent[4], extent[5]));
386}
387
388namespace {
389
393struct FcbAttribute {
394 ::flatbuffers::Offset<::flatbuffers::Vector<std::uint8_t>> attr_offset;
395 std::optional<AttributeSchema> own_schema;
396};
397
398FcbAttribute to_fcb_attribute(::flatbuffers::FlatBufferBuilder& fbb,
399 const nlohmann::ordered_json& attr, const AttributeSchema& schema) {
400 bool is_own_schema = false;
401 for (const auto& [key, val] : attr.items()) {
402 if (schema.find(key) == schema.end()) {
403 is_own_schema = true;
404 break;
405 }
406 }
407 if (is_own_schema) {
410 auto encoded = encode_attributes_with_schema(attr, own_schema);
411 return {fbb.CreateVector(encoded), std::move(own_schema)};
412 }
413 auto encoded = encode_attributes_with_schema(attr, schema);
414 return {fbb.CreateVector(encoded), std::nullopt};
415}
416
417} // namespace
418
419::flatbuffers::Offset<::CityObject> to_city_object(::flatbuffers::FlatBufferBuilder& fbb,
420 const std::string& id,
421 const nlohmann::ordered_json& co,
422 const AttributeSchema& attr_schema,
423 const AttributeSchema* semantic_attr_schema) {
424 auto id_off = fbb.CreateString(id);
425 auto [type_, extension_type_name] =
426 city_object_type_from_name(co.at("type").get<std::string>());
427 auto extension_type_off =
428 extension_type_name ? std::optional(fbb.CreateString(*extension_type_name)) : std::nullopt;
429
430 std::optional<::GeographicalExtent> extent;
431 if (auto it = co.find("geographicalExtent");
432 it != co.end() && it->is_array() && it->size() == 6) {
433 extent = to_geographical_extent({it->at(0).get<double>(), it->at(1).get<double>(),
434 it->at(2).get<double>(), it->at(3).get<double>(),
435 it->at(4).get<double>(), it->at(5).get<double>()});
436 }
437
438 std::optional<::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::Geometry>>>>
439 geometry_off;
440 std::optional<
441 ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::GeometryInstance>>>>
442 geometry_instances_off;
443 if (auto git = co.find("geometry"); git != co.end() && git->is_array()) {
444 // TWO FULL PASSES, matching Rust's `to_city_object` exactly
445 // (writer/serializer.rs:644-670): it filters "geometry" into a
446 // non-instance list and an instance list FIRST, builds every
447 // non-instance table AND CREATES THEIR VECTOR, and ONLY THEN builds
448 // every instance table and creates ITS vector. A single interleaved
449 // pass (building each entry as visited, in original array order)
450 // produces a DIFFERENT physical child allocation order in the
451 // FlatBuffer whenever instances and non-instances are interleaved in
452 // the source. Just as importantly, the "geoms" vector must be
453 // CREATED before any `to_geometry_instance` call runs, not after
454 // both loops finish -- Rust creates it immediately once the
455 // non-instance loop's `.collect()` completes, so every
456 // `fbb.CreateVector`/`CreateString` call inside `to_geometry_instance`
457 // happens strictly AFTER it on the Rust side too. The two orderings
458 // are not observably different by DECODED content, only by byte
459 // layout, so no functional test catches a regression here; only a
460 // byte-exact oracle over a file with interleaved geometry does (see
461 // test_writer_oracle.cpp's "interleaved geometry" case, which
462 // caught exactly this once already).
463 std::vector<::flatbuffers::Offset<::Geometry>> geoms;
464 for (const auto& g : *git)
465 if (g.at("type").get<std::string>() != "GeometryInstance")
466 geoms.push_back(to_geometry(fbb, g, semantic_attr_schema));
467 geometry_off = fbb.CreateVector(geoms);
468
469 std::vector<::flatbuffers::Offset<::GeometryInstance>> instances;
470 for (const auto& g : *git)
471 if (g.at("type").get<std::string>() == "GeometryInstance")
472 instances.push_back(to_geometry_instance(fbb, g));
473 // Both created -- even empty -- whenever "geometry" is present at
474 // all, matching Rust's Option<Vec<_>> filtered from ONE Option: it
475 // is the presence of the key, not either resulting list's own
476 // emptiness, that decides presence on the wire.
477 geometry_instances_off = fbb.CreateVector(instances);
478 }
479
480 std::optional<::flatbuffers::Offset<::flatbuffers::Vector<std::uint8_t>>> attributes_off;
481 std::optional<::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::Column>>>>
482 columns_off;
483 if (auto ait = co.find("attributes"); ait != co.end() && ait->is_object()) {
484 FcbAttribute fcb_attr = to_fcb_attribute(fbb, *ait, attr_schema);
485 attributes_off = fcb_attr.attr_offset;
486 if (fcb_attr.own_schema)
487 columns_off = to_columns(fbb, *fcb_attr.own_schema);
488 }
489
490 std::optional<
491 ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>>>>
492 children_off;
493 if (auto it = co.find("children"); it != co.end() && it->is_array()) {
494 std::vector<::flatbuffers::Offset<::flatbuffers::String>> c;
495 for (const auto& s : *it)
496 c.push_back(fbb.CreateString(s.get<std::string>()));
497 children_off = fbb.CreateVector(c);
498 }
499
500 // "children_roles" (CityObjectGroup only; the cjseq field is
501 // `children_roles`, snake_case like the FlatBuffers field itself, NOT
502 // camelCase -- confirmed against the cjseq2 source during the M3 codex
503 // review, correcting an earlier unverified guess of "childrenRoles").
504 // An unspecified role is `null` in CityJSON; the header has no way to
505 // spell that, so it is written as the empty string, mirroring the
506 // equivalent handling for point-of-contact strings elsewhere in this
507 // writer.
508 std::optional<
509 ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>>>>
510 children_roles_off;
511 if (auto it = co.find("children_roles"); it != co.end() && it->is_array()) {
512 std::vector<::flatbuffers::Offset<::flatbuffers::String>> r;
513 for (const auto& role : *it)
514 r.push_back(
515 fbb.CreateString(role.is_string() ? role.get<std::string>() : std::string()));
516 children_roles_off = fbb.CreateVector(r);
517 }
518
519 std::optional<
520 ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>>>>
521 parents_off;
522 if (auto it = co.find("parents"); it != co.end() && it->is_array()) {
523 std::vector<::flatbuffers::Offset<::flatbuffers::String>> p;
524 for (const auto& s : *it)
525 p.push_back(fbb.CreateString(s.get<std::string>()));
526 parents_off = fbb.CreateVector(p);
527 }
528
529 return CreateCityObject(fbb, type_, extension_type_off.value_or(0), id_off,
530 extent ? &*extent : nullptr, geometry_off.value_or(0),
531 geometry_instances_off.value_or(0), attributes_off.value_or(0),
532 columns_off.value_or(0), children_off.value_or(0),
533 children_roles_off.value_or(0), parents_off.value_or(0));
534}
535
536std::pair<::flatbuffers::Offset<::CityFeature>, NodeItem>
537to_fcb_city_feature(::flatbuffers::FlatBufferBuilder& fbb, const std::string& id,
538 const nlohmann::ordered_json& city_feature, const AttributeSchema& attr_schema,
539 const AttributeSchema* semantic_attr_schema) {
540 auto id_off = fbb.CreateString(id);
541
542 // `CityObjects` is a JSON object, so visited in ascending id order for
543 // reproducibility -- same determinism reasoning as
544 // cityfeature_to_index_entries (writer/attribute.hpp).
545 static const nlohmann::ordered_json kEmptyObjects = nlohmann::ordered_json::object();
546 auto co_it = city_feature.find("CityObjects");
547 const nlohmann::ordered_json& city_objects =
548 (co_it != city_feature.end() && co_it->is_object()) ? *co_it : kEmptyObjects;
549
550 std::vector<std::string> object_ids;
551 object_ids.reserve(city_objects.size());
552 for (const auto& [oid, unused] : city_objects.items())
553 object_ids.push_back(oid);
554 std::sort(object_ids.begin(), object_ids.end());
555
556 std::vector<::flatbuffers::Offset<::CityObject>> objects;
557 objects.reserve(object_ids.size());
558 for (const auto& oid : object_ids)
559 objects.push_back(
560 to_city_object(fbb, oid, city_objects.at(oid), attr_schema, semantic_attr_schema));
561 auto objects_off = fbb.CreateVector(objects);
562
563 std::vector<::Vertex> fb_vertices;
564 double min_x = 0, min_y = 0, max_x = 0, max_y = 0;
565 bool first = true;
566 if (auto it = city_feature.find("vertices"); it != city_feature.end()) {
567 fb_vertices.reserve(it->size());
568 for (const auto& v : *it) {
569 const double x = v.at(0).get<double>();
570 const double y = v.at(1).get<double>();
571 fb_vertices.emplace_back(v.at(0).get<std::int32_t>(), v.at(1).get<std::int32_t>(),
572 v.at(2).get<std::int32_t>());
573 if (first) {
574 min_x = max_x = x;
575 min_y = max_y = y;
576 first = false;
577 } else {
578 min_x = std::min(min_x, x);
579 max_x = std::max(max_x, x);
580 min_y = std::min(min_y, y);
581 max_y = std::max(max_y, y);
582 }
583 }
584 }
585 auto vertices_off = fbb.CreateVectorOfStructs(fb_vertices);
586
587 std::optional<::flatbuffers::Offset<::Appearance>> appearance_off;
588 if (auto it = city_feature.find("appearance"); it != city_feature.end() && it->is_object())
589 appearance_off = to_appearance(fbb, *it);
590
591 NodeItem bbox{min_x, min_y, max_x, max_y, 0};
592 auto feature_off =
593 CreateCityFeature(fbb, id_off, objects_off, vertices_off, appearance_off.value_or(0));
594 return {feature_off, bbox};
595}
596
597} // namespace fcb
598
599#endif // FCB_WITH_JSON
Every failure the library reports is one of these.
Definition error.hpp:30
::flatbuffers::Offset<::flatbuffers::Vector< std::uint8_t > > attr_offset
std::optional< AttributeSchema > own_schema
UIntView shells
Definition geometry.cpp:123
UIntView strings
Definition geometry.cpp:125
UIntView surfaces
Definition geometry.cpp:124
UIntView vertices
Definition geometry.cpp:126
std::size_t surface
Definition geometry.cpp:68
GeometryKind geometry_kind_from_name(const std::string &name)
Maps a CityJSON geometry type string to GeometryKind.
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...
::flatbuffers::Offset<::CityObject > to_city_object(::flatbuffers::FlatBufferBuilder &fbb, const std::string &id, const nlohmann::ordered_json &co, const AttributeSchema &attr_schema, const AttributeSchema *semantic_attr_schema)
Builds one CityObject table: type, geographical extent, geometry (split into non-instance and Geometr...
::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.
EncodedGeometry encode(const nlohmann::ordered_json &geometry)
Flattens one CityJSON geometry object – its boundaries and whatever semantics, material and texture i...
::flatbuffers::Offset<::GeometryInstance > to_geometry_instance(::flatbuffers::FlatBufferBuilder &fbb, const nlohmann::ordered_json &geometry)
Builds a GeometryInstance table: the template index, the 4x4 transformation matrix,...
GeometryKind
The FlatBuffers GeometryType enumerators, mirrored here so a caller can name a geometry type without ...
Definition geometry.hpp:22
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 ...
CoType city_object_type_from_name(const std::string &name)
Maps a CityJSON CityObject type string to the FlatBuffers tag.
::flatbuffers::Offset<::Appearance > to_appearance(::flatbuffers::FlatBufferBuilder &fbb, const nlohmann::ordered_json &appearance)
Builds the Appearance table (materials, textures, UV vertices, default themes) from a CityJSON appear...
::flatbuffers::Offset<::Geometry > to_geometry(::flatbuffers::FlatBufferBuilder &fbb, const nlohmann::ordered_json &geometry, const AttributeSchema *semantic_attr_schema)
Builds one Geometry table – boundaries, and whatever semantics, material and texture it carries – fro...
::GeographicalExtent to_geographical_extent(const std::array< double, 6 > &extent)
Builds a GeographicalExtent struct from a 6-element [minx,miny,minz,maxx,maxy,maxz] array.
std::map< std::string, std::pair< std::uint16_t, ::ColumnType > > AttributeSchema
Attribute schema: name -> (column index, column type).
Definition attribute.hpp:42
std::pair<::flatbuffers::Offset<::CityFeature >, NodeItem > to_fcb_city_feature(::flatbuffers::FlatBufferBuilder &fbb, const std::string &id, const nlohmann::ordered_json &city_feature, const AttributeSchema &attr_schema, const AttributeSchema *semantic_attr_schema)
Builds one CityFeature table – its CityObjects (visited in ascending id order), vertices,...
SurfaceType semantic_surface_type_from_name(const std::string &name)
Maps a CityJSON semantic surface type string to the FlatBuffers tag.
KeyKind kind
Definition stree.cpp:166
KeyValue key
Definition stree.cpp:56
A CityObjectType tag, plus the verbatim CityJSON name when the tag is ExtensionObject (which has no s...
Everything one CityJSON geometry object flattens to.
std::optional< std::vector< TextureMapping > > textures
std::optional< GMSemantics > semantics
std::optional< std::vector< MaterialMapping > > materials
std::vector< std::uint32_t > strings
std::vector< std::uint32_t > shells
std::vector< std::uint32_t > solids
std::vector< std::uint32_t > surfaces
std::vector< std::uint32_t > indices
One R-tree node entry: 4 doubles then a u64, all little-endian, 40 bytes with no padding (packed_rtre...
A SemanticSurfaceType tag, plus the verbatim CityJSON name when the tag is ExtraSemanticSurface.