Query GeoParquet and raw WKB

Load the spatial extension before reading geometry data:

# Prerequisite: connection is an open DuckDB connection, as in the tutorials.
# Install the extension if needed; loading enables spatial functions and types.
connection.execute("INSTALL spatial")
connection.execute("LOAD spatial")

For a fixed, application-controlled path, create a view:

-- Read a metadata-bearing GeoParquet file through a view named items.
CREATE VIEW items AS SELECT * FROM read_parquet('items.parquet');
-- Inspect the SQL type DuckDB exposes before applying a spatial predicate.
SELECT typeof(geometry) FROM items LIMIT 1;

Inspect the type before filtering. With the spatial extension loaded, the verified GeoParquet files expose GEOMETRY; DuckDB 1.5.6 may expose a CRS-qualified geometry type. They can be filtered directly.

Arrow exports can change the representation, so inspect any re-registered table again.

If the column is raw WKB BLOB, create a decoding view:

-- Use this alternative view for raw WKB instead of the GeoParquet view above.
-- Keep other columns and replace the binary geometry with decoded GEOMETRY.
CREATE VIEW items AS
SELECT * EXCLUDE (geometry), ST_GeomFromWKB(geometry) AS geometry
FROM read_parquet('raw-wkb.parquet');

With either view created on your open connection, parse a spatial filter, map its property to the view's geometry column, and execute the predicate:

from pygeofilter import ast
from pygeofilter.parsers.cql2_json import parse
from pygeofilter_duckdb import to_sql_where_params

# Select geometries that intersect point (0, 0). Use coordinates in the same
# CRS as your data, and replace this point with your desired search geometry.
root = parse({
    "op": "s_intersects",
    "args": [
        {"property": "geometry"},
        {"type": "Point", "coordinates": [0, 0]},
    ],
})
assert isinstance(root, ast.Node)

# The items view exposes native GEOMETRY for both ingestion paths above.
# Bind the filter geometry separately from the generated SQL predicate.
predicate, parameters = to_sql_where_params(root, {"geometry": "geometry"})

# Execute against the existing connection and view. This assumes an id column;
# replace it with your dataset's identifier column if necessary.
# Export matching geometries as WKB for binary consumers such as Shapely.
rows = connection.execute(
    "SELECT id, ST_AsWKB(geometry) AS geometry FROM items WHERE "
    + predicate
    + " ORDER BY id",
    parameters,
).fetchall()
print(rows)

Each result contains an ID and WKB geometry. Disjoint and null geometries are excluded by the intersection predicate.

Field mappings are identifiers, so a value such as ST_GeomFromWKB(geometry) is not a supported field mapping.

Export geometry explicitly when consumers need WKB:

-- Encode native geometry as WKB for binary consumers such as Shapely.
-- Null geometries remain NULL in the exported column.
SELECT id, ST_AsWKB(geometry) AS geometry FROM items;

ST_AsWKB encodes native geometry; ST_GeomFromWKB decodes WKB. Conversion does not reproject coordinates. See geometry storage for CRS and null behavior and the DuckDB spatial reference for function details.