Skip to content
CityParquet is v0.1.0-draft — the specification is still changing.
Esc
↑↓navigate↵open⌘Jpreview
On this page

D · Appearance & shared resources

Geometry separated from appearance; shared resources in deduplicated sidecars.

How CityParquet handles resources shared across many objects: materials and textures are separated from geometry and deduplicated into sidecar files referenced by global id, and the shared relative geometries of implicit geometries live in their own sidecar, with each object carrying only a small implicit_geometry struct. Prior art weighed here: 3DCityDB’s appearance table and CityJSON’s shared pools of appearance and of implicit-geometry shapes, following the OBJ / COLLADA / glTF principle of separating geometry from appearance.

Geometry separated from appearance; shared resources in deduplicated sidecars

decided

Decision. materials.parquet and textures.parquet hold reusable definitions; the object table’s nullable material_lod*/texture_lod* columns reference them by the sidecar’s dataset-global id — matched against the id column, never a row position. The columns are typed MAPs keyed by theme whose values are flat per WKB face, in WKB face order — MAP<VARCHAR, LIST<BIGINT>> for materials, and per face → per ring STRUCT<id BIGINT, uv LIST<LIST<DOUBLE>>> for textures, with the UV coordinates inlined. See the specification → material / texture columns.

Alternatives considered.

  • 3DCityDB keeps a separate appearance table with a foreign key to the feature table.
  • CityJSON keeps an appearance object with a shared material/texture pool referenced by feature-local indices.
  • JSON cells nested as CityJSON nests them (per shell for a Solid, per solid and shell for a MultiSolid): a consumer must know the geometry type to find a face’s material, which is the traversal the flattened face_semantics exists to avoid, and the shell partition is stored twice.
  • JSON cells, flat per face: the right shape, but a JSON parse on every analytical query over values whose shape is fixed and well known.

Why this. Follows the recurring project principle — geometry and appearance should be separable, as in OBJ / COLLADA / glTF. Materials and textures are referenced by many objects, so storing them once and referencing beats duplicating them per object; global dedup (by canonical JSON) is the columnar analogue of CityJSON’s shared appearance pool. Two normalisations were needed relative to the source: references become dataset-global sidecar id values (not CityJSONSeq’s feature-local indices), and UV indices are replaced inline by the actual [u, v] pairs so the object table is self-contained. The column shape follows face_semantics: one positional model for everything attached to a WKB face, and a typed column because a theme’s content has a fixed shape — only the theme key is open, which is what MAP expresses.

Trade-off. Resolving appearance needs a sidecar lookup; because indices are global, the exporter must re-intern feature-local pools (and re-derive UV indices into a vertices-texture pool) on the way back to CityJSON.

Status. Decided. The global-id + inline-UV normalisation and the typed per-face column shape are normative; the support table records how far the reference implementation has caught up.

Implicit geometries: relative geometries in a sidecar, the rest in an implicit_geometry struct

decided

Decision. Following the CityGML ImplicitGeometry, an implicit geometry is a reference point, a transformation matrix and a reference to a shared relative geometry. The relative geometries live in implicit_geometries.parquet, one per row, using the same geometry strategy as the object table — including the per-LoD column grammar: geometry_lod* + nullable geometry_properties_lod* / material_lod* / texture_lod*. An object with an implicit geometry stores an implicit_geometry STRUCT {id, point (WKB PointZ), transformationMatrix (LIST<DOUBLE>, a flat 16-element row-major 4×4)} in the object table. See the specification → implicit_geometries.parquet.

Alternatives considered. Inline the relative geometry at each object (simple, but defeats the whole point of an implicit geometry) vs reference a shared relative geometry (chosen).

Why this. Implicit geometries exist precisely to share one geometry across many objects; a sidecar of relative geometries referenced by id preserves that compaction. A relative geometry’s vertices are in local coordinates and are not subject to any dataset-level transform, so its WKB stores raw doubles. The sidecar follows the per-LoD column grammar so that one LoD-naming rule holds across the whole format, rather than making this the sole place where an LoD rides in a value.

Trade-off. Resolving an implicit geometry needs a sidecar lookup plus applying its transformationMatrix and reference point. A relative geometry is one geometry at one LoD, so the per-LoD columns leave each row sparse — accepted as the price of the uniform naming rule.

Status. Decided and implemented; 🟡 an object with several implicit geometries remains an open question, since implicit_geometry is a single struct.

Last updated on October 12, 2026

Was this page helpful?