FlatCityBuf C++ reader 0.8.0
Native C++17 reader for FlatCityBuf, the cloud-optimized CityJSON format
Loading...
Searching...
No Matches
geometry.cpp
Go to the documentation of this file.
1#include <fcb/generated/geometry_generated.h>
2#include <fcb/geometry.hpp>
3
4#include <algorithm>
5
6namespace fcb {
7
8namespace {
9
10// GeometryKind is declared in the public header so callers need not include
11// the generated flatbuffers code; it must stay in lock-step with it.
12//
13// ALL EIGHT are asserted, not a sample. Spot-checking the ends would let a
14// SWAP through: exchanging Solid(4) and MultiSolid(5) in geometry.fbs keeps
15// the first and last enumerators in place while silently inverting the exact
16// depths this file exists to get right.
17#define FCB_ASSERT_KIND(name) \
18 static_assert(static_cast<std::uint8_t>(GeometryKind::name) == \
19 static_cast<std::uint8_t>(::GeometryType::name), \
20 "GeometryKind::" #name " has drifted from the generated GeometryType")
29#undef FCB_ASSERT_KIND
30// And that there are still exactly eight: a NEW enumerator appended to the
31// .fbs adds a depth this reader has never seen and must be considered here.
32static_assert(static_cast<std::uint8_t>(::GeometryType::MAX) ==
33 static_cast<std::uint8_t>(GeometryKind::GeometryInstance),
34 "geometry.fbs gained a GeometryType; give it a depth in this file");
35
36#ifdef FCB_WITH_JSON
37
38[[noreturn]] void overrun(const char* what) {
40 std::string("geometry boundaries overrun in ") + what);
41}
42
45nlohmann::json index_to_json(std::uint32_t v) {
46 if (v == UINT32_MAX)
47 return nullptr;
48 return v;
49}
50
57std::uint32_t count_at(UIntView counts, std::size_t& cursor) {
58 const std::uint32_t n = (cursor < counts.size()) ? counts[cursor] : 0;
59 ++cursor;
60 return n;
61}
62
63// ------------------------------------------------------------ boundaries ---
64
66struct Cursors {
67 std::size_t shell = 0;
68 std::size_t surface = 0;
69 std::size_t ring = 0;
70 std::size_t index = 0;
71};
72
74nlohmann::json take_ring(UIntView strings, UIntView indices, Cursors& c) {
75 if (c.ring >= strings.size())
76 overrun("strings");
77 const std::uint32_t ring_size = strings[c.ring++];
78
79 if (c.index > indices.size() || indices.size() - c.index < ring_size) {
80 overrun("indices");
81 }
82 auto ring = nlohmann::json::array();
83 for (std::uint32_t i = 0; i < ring_size; ++i) {
84 ring.push_back(indices[c.index + i]);
85 }
86 c.index += ring_size;
87 return ring;
88}
89
91nlohmann::json take_surface(UIntView surfaces, UIntView strings, UIntView indices, Cursors& c) {
92 if (c.surface >= surfaces.size())
93 overrun("surfaces");
94 const std::uint32_t ring_count = surfaces[c.surface++];
95
96 auto surface = nlohmann::json::array();
97 for (std::uint32_t i = 0; i < ring_count; ++i) {
98 surface.push_back(take_ring(strings, indices, c));
99 }
100 return surface;
101}
102
104nlohmann::json take_shell(UIntView shells, UIntView surfaces, UIntView strings, UIntView indices,
105 Cursors& c) {
106 if (c.shell >= shells.size())
107 overrun("shells");
108 const std::uint32_t surface_count = shells[c.shell++];
109
110 auto shell = nlohmann::json::array();
111 for (std::uint32_t i = 0; i < surface_count; ++i) {
112 shell.push_back(take_surface(surfaces, strings, indices, c));
113 }
114 return shell;
115}
116
117// --------------------------------------------------------------- texture ---
118
122struct TexCursors {
127 std::size_t shell = 0;
128 std::size_t surface = 0;
129 std::size_t string = 0;
130 std::size_t vertex = 0;
131
132 nlohmann::json take_ring() {
133 const std::uint32_t size = count_at(strings, string);
134 const std::size_t end = std::min(vertex + size, vertices.size());
135 auto ring = nlohmann::json::array();
136 for (; vertex < end; ++vertex)
137 ring.push_back(index_to_json(vertices[vertex]));
138 return ring;
139 }
140
141 nlohmann::json take_surface() {
142 const std::uint32_t rings = count_at(surfaces, surface);
143 auto out = nlohmann::json::array();
144 for (std::uint32_t i = 0; i < rings; ++i)
145 out.push_back(take_ring());
146 return out;
147 }
148
149 nlohmann::json take_shell() {
150 const std::uint32_t n = count_at(shells, shell);
151 auto out = nlohmann::json::array();
152 for (std::uint32_t i = 0; i < n; ++i)
153 out.push_back(take_surface());
154 return out;
155 }
156};
157
158#endif // FCB_WITH_JSON
159
160} // namespace
161
162#ifdef FCB_WITH_JSON
163
166 Cursors c;
167 auto out = nlohmann::json::array();
168
169 switch (type) {
171 // Every index is a point of the one and only ring.
172 for (std::size_t i = 0; i < indices.size(); ++i)
173 out.push_back(indices[i]);
174 return out;
175
177 // One ring per `strings` entry. `surfaces` holds one redundant
178 // entry (== strings.size()); it is ignored.
179 for (std::size_t i = 0; i < strings.size(); ++i) {
180 out.push_back(take_ring(strings, indices, c));
181 }
182 return out;
183
186 // One surface per `surfaces` entry; `shells` is the redundant one.
187 for (std::size_t i = 0; i < surfaces.size(); ++i) {
188 out.push_back(take_surface(surfaces, strings, indices, c));
189 }
190 return out;
191
194 // `solids[i]` shells in the i-th solid. Nothing above it.
195 for (std::size_t i = 0; i < solids.size(); ++i) {
196 auto solid = nlohmann::json::array();
197 for (std::uint32_t k = 0; k < solids[i]; ++k) {
198 solid.push_back(take_shell(shells, surfaces, strings, indices, c));
199 }
200 out.push_back(std::move(solid));
201 }
202 return out;
203
206 default:
207 // One shell per `shells` entry; `solids` is the redundant one.
208 //
209 // UNKNOWN-TAG POLICY. The `default:` here is a C++ formality, not
210 // a fallback with a meaning: an unknown geometry tag never reaches
211 // this function, because geometry_to_json() calls
212 // geometry_type_name() first and that throws (see below, and the
213 // policy note at the top of cityjson.cpp). Treating an unknown tag
214 // as a Solid would read its boundaries at the wrong depth and hand
215 // the caller a plausible-looking lie; the reference errors for the
216 // same reason (geom_decoder.rs::GeometryType::to_cj).
217 for (std::size_t i = 0; i < shells.size(); ++i) {
218 out.push_back(take_shell(shells, surfaces, strings, indices, c));
219 }
220 return out;
221 }
222}
223
225 UIntView values) {
226 std::size_t cursor = 0;
227 auto take = [&](std::uint32_t n) {
228 const std::size_t end = std::min(cursor + n, values.size());
229 auto out = nlohmann::json::array();
230 for (; cursor < end; ++cursor)
231 out.push_back(index_to_json(values[cursor]));
232 return out;
233 };
234
235 auto out = nlohmann::json::array();
236 switch (type) {
238 // One array per shell.
239 for (std::size_t i = 0; i < shells.size(); ++i)
240 out.push_back(take(shells[i]));
241 return out;
242
245 // One array per shell, per solid.
246 std::size_t shell_cursor = 0;
247 for (std::size_t i = 0; i < solids.size(); ++i) {
248 auto solid = nlohmann::json::array();
249 for (std::uint32_t k = 0; k < solids[i]; ++k) {
250 solid.push_back(take(count_at(shells, shell_cursor)));
251 }
252 out.push_back(std::move(solid));
253 }
254 return out;
255 }
256
257 // MultiPoint, MultiLineString, MultiSurface, CompositeSurface: one
258 // value per surface, flat. A GeometryInstance carries no semantics of
259 // its own; its template does, so the reference reads that flat too.
260 default:
261 for (std::size_t i = 0; i < values.size(); ++i) {
262 out.push_back(index_to_json(values[i]));
263 }
264 return out;
265 }
266}
267
270 std::size_t vertex = 0;
271 auto take_shell = [&](std::uint32_t n) {
272 const std::size_t end = std::min(vertex + n, vertices.size());
273 auto out = nlohmann::json::array();
274 for (; vertex < end; ++vertex)
275 out.push_back(index_to_json(vertices[vertex]));
276 return out;
277 };
278 auto flat = [&] {
279 auto out = nlohmann::json::array();
280 for (std::size_t i = 0; i < vertices.size(); ++i) {
281 out.push_back(index_to_json(vertices[i]));
282 }
283 return out;
284 };
285
286 auto out = nlohmann::json::array();
287 switch (type) {
289 // One array per shell; a UINT32_MAX count is a whole null shell.
290 for (std::size_t i = 0; i < shells.size(); ++i) {
291 if (shells[i] == UINT32_MAX) {
292 out.push_back(nullptr);
293 } else {
294 out.push_back(take_shell(shells[i]));
295 }
296 }
297 return out;
298
301 // One array per shell, per solid. Null at either level.
302 std::size_t shell_cursor = 0;
303 for (std::size_t i = 0; i < solids.size(); ++i) {
304 if (solids[i] == UINT32_MAX) {
305 out.push_back(nullptr);
306 continue;
307 }
308 auto solid = nlohmann::json::array();
309 for (std::uint32_t k = 0; k < solids[i]; ++k) {
310 const std::uint32_t n = count_at(shells, shell_cursor);
311 if (n == UINT32_MAX) {
312 solid.push_back(nullptr);
313 } else {
314 solid.push_back(take_shell(n));
315 }
316 }
317 out.push_back(std::move(solid));
318 }
319 return out;
320 }
321
322 // MultiSurface and CompositeSurface get one index per surface.
323 // MultiPoint, MultiLineString and GeometryInstance cannot carry a
324 // material at all; if one is somehow present it has no depth of its
325 // own, so it is read as the shallowest thing it could be.
326 default:
327 return flat();
328 }
329}
330
333 TexCursors c{shells, surfaces, strings, vertices, 0, 0, 0, 0};
334
335 auto out = nlohmann::json::array();
336 switch (type) {
339 // Per surface, per ring.
340 for (std::size_t i = 0; i < surfaces.size(); ++i)
341 out.push_back(c.take_surface());
342 return out;
343
345 // ... per shell.
346 for (std::size_t i = 0; i < shells.size(); ++i)
347 out.push_back(c.take_shell());
348 return out;
349
352 // ... per solid.
353 for (std::size_t i = 0; i < solids.size(); ++i) {
354 auto solid = nlohmann::json::array();
355 for (std::uint32_t k = 0; k < solids[i]; ++k)
356 solid.push_back(c.take_shell());
357 out.push_back(std::move(solid));
358 }
359 return out;
360
361 // MultiPoint, MultiLineString and GeometryInstance cannot carry a
362 // texture; read whatever is there at the shallowest legal depth. The
363 // `max(1)` is the reference's (geom_decoder.rs:542) and is what makes
364 // a textureless count array still produce one empty surface.
365 default: {
366 const std::size_t n = std::max<std::size_t>(surfaces.size(), 1);
367 for (std::size_t i = 0; i < n; ++i)
368 out.push_back(c.take_surface());
369 return out;
370 }
371 }
372}
373
374#endif // FCB_WITH_JSON
375
376std::string geometry_type_name(std::uint8_t type) {
377 switch (static_cast<::GeometryType>(type)) {
378 case ::GeometryType::MultiPoint:
379 return "MultiPoint";
380 case ::GeometryType::MultiLineString:
381 return "MultiLineString";
382 case ::GeometryType::MultiSurface:
383 return "MultiSurface";
384 case ::GeometryType::CompositeSurface:
385 return "CompositeSurface";
386 case ::GeometryType::Solid:
387 return "Solid";
388 case ::GeometryType::MultiSolid:
389 return "MultiSolid";
390 case ::GeometryType::CompositeSolid:
391 return "CompositeSolid";
392 case ::GeometryType::GeometryInstance:
393 return "GeometryInstance";
394 }
395 // UNKNOWN-TAG POLICY. Unlike a City Object type or a semantic surface
396 // type, a geometry type has no CityJSON extension escape hatch: the spec
397 // (section 3) enumerates exactly these eight `type` values and admits no
398 // others, so there is no '+'-prefixed name a reader could legally emit
399 // here. Erroring is the only option that neither mislabels the geometry
400 // nor writes a document a validator rejects. The Rust reader now does the
401 // same (geom_decoder.rs::GeometryType::to_cj, Error::UnknownEnumTag).
402 throw Error(ErrorCode::InvalidFlatbuffer, "unknown geometry type " + std::to_string(type));
403}
404
405} // 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
std::size_t size() const noexcept
Definition span.hpp:26
#define FCB_ASSERT_KIND(name)
Definition geometry.cpp:17
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 shell
Definition geometry.cpp:67
std::size_t vertex
Definition geometry.cpp:130
std::size_t surface
Definition geometry.cpp:68
std::size_t index
Definition geometry.cpp:70
std::size_t ring
Definition geometry.cpp:69
nlohmann::json decode_boundaries(GeometryKind type, UIntView solids, UIntView shells, UIntView surfaces, UIntView strings, UIntView indices)
Rebuild CityJSON's nested boundaries from the five flattened arrays, at the depth type implies.
Definition geometry.cpp:164
nlohmann::json decode_texture_values(GeometryKind type, UIntView solids, UIntView shells, UIntView surfaces, UIntView strings, UIntView vertices)
Rebuild the values array of one texture theme from a TextureMapping.
Definition geometry.cpp:331
span< const std::uint32_t > UIntView
Definition geometry.hpp:16
nlohmann::json decode_material_values(GeometryKind type, UIntView solids, UIntView shells, UIntView vertices)
Rebuild the values array of one material theme from a MaterialMapping.
Definition geometry.cpp:268
nlohmann::json decode_semantics_values(GeometryKind type, UIntView solids, UIntView shells, UIntView values)
Rebuild the nested semantics values array from the flat run of semantic indices.
Definition geometry.cpp:224
GeometryKind
The FlatBuffers GeometryType enumerators, mirrored here so a caller can name a geometry type without ...
Definition geometry.hpp:22
std::string geometry_type_name(std::uint8_t type)
CityJSON name for a GeometryType enumerator, e.g.
Definition geometry.cpp:376