Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ duckdb_unittest_tempdir/
testext
test/python/__pycache__/
.Rhistory

# Wasm toolchain installed by `just wasm-setup` (emsdk + vcpkg).
.vendor
4 changes: 1 addition & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ set(EXTENSION_SOURCES
src/functions/geom_accessors.cpp
src/functions/distance.cpp
src/functions/transforms.cpp
src/functions/arrow_native.cpp
src/functions/struct_metadata.cpp
src/kernel/solid_model.cpp
src/kernel/payload.cpp
Expand All @@ -42,8 +41,7 @@ set(EXTENSION_SOURCES
src/kernel/geom_construct.cpp
src/kernel/geom_analysis.cpp
src/kernel/geom_serialize.cpp
src/kernel/crs_transform.cpp
src/kernel/arrow_native_import.cpp)
src/kernel/crs_transform.cpp)

# PROJ backs ST_3DTransform (kernel/crs_transform.cpp). Under the vcpkg toolchain
# the vcpkg copy is found first; on a bare macOS dev box fall back to Homebrew.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ Full build, test, and distribution notes: [docs/README.md](docs/README.md).
| --- | --- |
| [docs/FUNCTIONS.md](docs/FUNCTIONS.md) | **Function reference** — every function, with signatures and runnable examples |
| [docs/EXAMPLE.md](docs/EXAMPLE.md) | Hands-on walkthrough against real 3DBAG data |
| [docs/TESTING.md](docs/TESTING.md) | Manual notebook: every public function run against real CityJSON, CityJSONSeq, CityParquet and arrow-native data |
| [docs/TESTING.md](docs/TESTING.md) | Manual notebook: every public function run against real CityJSON, CityJSONSeq and CityParquet data |
| [docs/DESIGN_DOC.md](docs/DESIGN_DOC.md) | Architecture & design philosophy: type model, layering, invariants |
| [docs/CITYJSON_INTEROP.md](docs/CITYJSON_INTEROP.md) | Composing with the `cityjson` extension; running the interop tests |
| [docs/FUTURE_WORK.md](docs/FUTURE_WORK.md) | Deferred design decisions |
Expand Down
8 changes: 0 additions & 8 deletions docs/DESIGN_DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,6 @@ cavities. The sidecar's `shells` key restores that grouping, which in turn enabl
This keeps `duckdb-3d` ignorant of CityJSON files, LoD selection, and semantic surfaces —
all of which stay upstream.

An **experimental** arrow-native ingestion path (`ST_3DFromArrowNative` and siblings) reads
nested `LIST`/`STRUCT` boundary columns directly, skipping WKB serialization entirely while
producing the identical payload. It is part of a cross-repo experiment with `cityparquet-rs`
and `duckdb-cityjson` and is **not** part of the settled v1 surface.

---

## 8. Validation & measurement semantics
Expand Down Expand Up @@ -457,6 +452,3 @@ surface; PROJ-backed `ST_3DTransform`. See the
booleans (union / difference / intersection), true 3D convex hulls, tessellation, straight
skeletons, medial axes, and topology-repair workflows. These are gated on whether to take on
a CGAL or SFCGAL dependency — a decision deliberately not yet made, per §2.4.

**Experimental.** Arrow-native ingestion (§7), pending the outcome of the cross-repo
experiment with `cityparquet-rs` and `duckdb-cityjson`.
84 changes: 45 additions & 39 deletions docs/FUNCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,11 @@ Convert between them via WKB: `ST_Geom3DFromWKB(ST_3DAsWKB(solid))` goes solid

## Conventions

**Null propagation.** Any `NULL` argument yields `NULL`, with two deliberate exceptions:
**Null propagation.** Any `NULL` argument yields `NULL`, with one deliberate exception:
`ST_3DFromWKB(wkb, NULL)` builds the solid *without* metadata and returns a non-`NULL`
result — a missing sidecar is not an error.

- `ST_3DFromWKB(wkb, NULL)` builds the solid *without* metadata and returns a non-`NULL`
result — a missing sidecar is not an error.
- The arrow-native constructors return `NULL` if *any* argument is `NULL`, because
`geometry_properties.type` is load-bearing there.

**`TRY` variants** (`ST_3DTryFromWKB`, `ST_3DTryFromArrowNative`, `ST_Geom3DTryFromArrowNative`)
catch **row-level** errors and return `NULL` instead. Bind-time errors — a malformed metadata
**`TRY` variants** (`ST_3DTryFromWKB`) catch **row-level** errors and return `NULL` instead. Bind-time errors — a malformed metadata
STRUCT, a wrong argument type — still raise. There is **no** `ST_Geom3DTryFromWKB`.

**Errors, not repair.** The extension never silently fixes geometry. `ST_3DVolume` on a
Expand Down Expand Up @@ -160,13 +156,51 @@ stay un-prefixed.

## Import / construction

The single-argument constructors take either a `BLOB` of WKB or DuckDB's native
`GEOMETRY`, so a GeoParquet column that arrives carrying the Parquet `GEOMETRY`
logical type needs no `ST_AsWKB` in between — see
[WKB or GEOMETRY](#wkb-or-geometry) below.

| Function | Signature | Returns |
| --- | --- | --- |
| `ST_3DFromWKB` | `(wkb BLOB)` | `SOLID_3D` |
| `ST_3DFromWKB` | `(wkb BLOB \| GEOMETRY)` | `SOLID_3D` |
| `ST_3DFromWKB` | `(wkb BLOB, geometry_properties VARCHAR)` | `SOLID_3D` |
| `ST_3DFromWKB` | `(wkb BLOB, geometry_properties STRUCT)` | `SOLID_3D` |
| `ST_3DTryFromWKB` | same three overloads | `SOLID_3D` or `NULL` |
| `ST_Geom3DFromWKB` | `(wkb BLOB)` | `GEOM_3D` |
| `ST_Geom3DFromWKB` | `(wkb BLOB \| GEOMETRY)` | `GEOM_3D` |

### WKB or GEOMETRY

A CityParquet package annotates its GeoParquet-legal geometry columns with the Parquet
`GEOMETRY` logical type, and DuckDB promotes such a column to its native `GEOMETRY` on
read. `geometry_lod0_0::BLOB` is not a way back — the cast is unimplemented — so the
single-argument constructors accept `GEOMETRY` directly:

```sql
SELECT count(*) AS n,
ROUND(max(abs(ST_3DFootprintArea(ST_Geom3DFromWKB(geometry_lod0_0))
- ST_3DFootprintArea(ST_Geom3DFromWKB(ST_AsWKB(geometry_lod0_0))))), 12) AS max_abs_diff
FROM read_parquet('building.parquet') WHERE geometry_lod0_0 IS NOT NULL;
```
```
┌───────┬──────────────┐
│ n │ max_abs_diff │
├───────┼──────────────┤
│ 1115 │ 0.0 │
└───────┴──────────────┘
```

Two consequences worth knowing:

- **Solid columns are unaffected**, because they never carry the annotation and could not
survive it: DuckDB's geometry model has no polyhedral surface, and `ST_GeomFromWKB`
raises `Unsupported geometry type in WKB` on solid bytes. A solid therefore cannot reach
these functions as `GEOMETRY` at all; on `ST_3DFromWKB` / `ST_3DTryFromWKB` the
`GEOMETRY` form matters for foreign GeoParquet columns, where `MultiPolygon Z` is common
and the `TRY` form's `NULL` is the useful answer.
- The argument is bound through a single `ANY` candidate rather than one overload per
type, so an untyped `ST_3DFromWKB(NULL)` still binds. Anything that is neither
`GEOMETRY` nor implicitly castable to `BLOB` is rejected at bind time as before.

### `ST_3DFromWKB` / `ST_3DTryFromWKB`

Expand Down Expand Up @@ -210,32 +244,6 @@ SELECT ST_3DGeometryType(ST_Geom3DFromWKB(geometry)) AS gtype FROM ex;
└──────────────────────┘
```

### Arrow-native constructors (experimental)

`ST_3DFromArrowNative`, `ST_3DTryFromArrowNative`, `ST_Geom3DFromArrowNative`,
`ST_Geom3DTryFromArrowNative` ingest nested `LIST`/`STRUCT` boundary columns plus a vertex
pool **directly, bypassing WKB** — while producing exactly the same payload.

```
ST_3DFromArrowNative(boundaries, vertices, geometry_properties) → SOLID_3D
```

- `boundaries` — `INTEGER[][][][][]` (solid → shell → face → ring → vertex index)
- `vertices` — `STRUCT(x DOUBLE, y DOUBLE, z DOUBLE)[]`
- `geometry_properties` — JSON `VARCHAR` **or** the CityParquet `STRUCT`; **required**

`geometry_properties.type` is load-bearing and dispatches solid-family
(`Solid`/`MultiSolid`/`CompositeSolid`, the `ST_3D*` pair) versus surface-family
(`MultiSurface`/`CompositeSurface`, the `ST_Geom3D*` pair). A single-shell `Solid` and a
padded `MultiSurface` are physically indistinguishable, so the family cannot be inferred from
shape — passing the wrong family raises (or yields `NULL` in the `TRY` form).

> **Experimental.** These are part of an in-progress cross-repo experiment with
> `cityparquet-rs` and `duckdb-cityjson`, not part of the settled v1 surface. The producing
> readers are not yet released upstream.

---

## Export / serialization

| Function | Signature | Returns | Notes |
Expand Down Expand Up @@ -702,16 +710,14 @@ FROM ex;

| Category | Functions |
| --- | --- |
| **Import** | `ST_3DFromWKB`, `ST_3DTryFromWKB`, `ST_Geom3DFromWKB`, `ST_3DFromArrowNative`*, `ST_3DTryFromArrowNative`*, `ST_Geom3DFromArrowNative`*, `ST_Geom3DTryFromArrowNative`* |
| **Import** | `ST_3DFromWKB`, `ST_3DTryFromWKB`, `ST_Geom3DFromWKB` |
| **Export** | `ST_3DAsWKB`, `ST_3DAsText`, `ST_3DAsGeoJSON`, `ST_3DAsBinary` |
| **Introspection** | `ST_3DBounds`, `ST_3DNumSolids`, `ST_3DNumShells`, `ST_3DNumFaces`, `ST_3DZMin`, `ST_3DZMax`, `ST_NDims`, `ST_3DHasZ`, `ST_CoordDim`, `ST_3DGeometryType`, `ST_3DDimension`, `ST_3DNumGeometries`, `ST_3DX`, `ST_3DY`, `ST_3DZ`, `ST_IsPlanar` |
| **Validation** | `ST_3DIsClosed`, `ST_3DIsManifold`, `ST_3DIsOriented`, `ST_3DValidationReport` |
| **Measurement** | `ST_3DVolume`, `ST_3DSurfaceArea`, `ST_3DArea`, `ST_3DFootprintArea`, `ST_3DPerimeter`, `ST_3DLength` |
| **Distance** | `ST_3DDistance`, `ST_3DMaxDistance`, `ST_3DDWithin`, `ST_3DDFullyWithin`, `ST_3DIntersects`, `ST_3DClosestPoint`, `ST_3DShortestLine` |
| **Transform / construct** | `ST_3DTranslate`, `ST_3DScale`, `ST_3DRotateX`, `ST_3DRotateY`, `ST_3DRotateZ`, `ST_3DTransform`, `ST_3DExtrude`, `ST_MakeSolid`, `ST_3DCentroid`, `ST_3DConvexHull`, `ST_Force3D` |

`*` experimental (arrow-native ingestion).

**Not implemented.** These PostGIS names appear in comparison tables but are **not**
registered: `ST_3DIsValid` (validity is a field of `ST_3DValidationReport`), `ST_3DUnion` /
`ST_3DIntersection` / `ST_3DDifference`, `ST_3DLongestLine`, `ST_3DExtent`, `ST_Affine`,
Expand Down
Loading
Loading