What native Parquet geometry types change for GeoParquet pipelines

Dark illustration of a columnar table on the left sending orange and blue lines through small geometry icons, including points, a line, polygons and a grid, into a stack of map layers showing buildings, rivers and boundaries.
Cover illustration; it does not show data from this article.

Apache Parquet now has GEOMETRY and GEOGRAPHY logical types, so a geometry column no longer has to be an anonymous binary column that only the GeoParquet geo metadata can explain. If you write spatial Parquet today, you have to decide when to switch your writers to the native types and when to keep producing GeoParquet 1.1, and that depends on which readers sit downstream.

This post separates the two layers, says where each spec stands as of 21 October 2026, and shows what GDAL 3.12.4 writes in each mode on a real OpenStreetMap extract. We did not finish the cross-reader half of the test. The section on what we could not test explains why.

Two layers: the Parquet type and the geo key

GeoParquet 1.x is metadata on top of Parquet. Geometries live in a BYTE_ARRAY column as WKB, or in GeoArrow-style structs in 1.1's "native encodings". A JSON document under the file-level geo key says which column is geometry, what its CRS is (PROJJSON), which geometry types it holds, and, since 1.1, which bbox struct column carries per-row bounding boxes. A reader that ignores the geo key sees bytes.

The Parquet format's own types work at a different level. Parquet format 2.11.0 (21 March 2025) added GEOMETRY and GEOGRAPHY to the schema itself. Per LogicalTypes.md and Geospatial.md:

  • Both annotate a BYTE_ARRAY holding ISO WKB (XY, XYZ, XYM, XYZM). The bytes on disk are the same kind of WKB GeoParquet 1.x used.
  • GEOMETRY has planar edges and one optional parameter, crs. If crs is unset, it means OGC:CRS84.
  • GEOGRAPHY has a crs parameter, which must be geographic, and an algorithm parameter for edge interpolation: SPHERICAL (default), VINCENTY, THOMAS, ANDOYER or KARNEY.
  • crs is a string in one of four suggested forms: inline PROJJSON, <authority>:<code> such as EPSG:4326, srid:<identifier>, or projjson:<key_name> pointing at another key in the file metadata.
  • Sort order is undefined, and writers should not write min/max statistics for these columns. Instead, each column chunk gets GeospatialStatistics, made of a bounding box (xmin, xmax, ymin and ymax required, Z and M optional) and a list of the geometry types present as WKB integer codes (for example [3, 6] for Polygon plus MultiPolygon, or 1003 for Polygon Z).
  • For bounding boxes that cross the antimeridian, xmin may be greater than xmax. Axis order is always x/longitude first, regardless of what the CRS says.

The practical change is that the schema now says "this is geometry, in this CRS". The footer also carries a per-row-group bounding box that any Parquet reader can use for pruning without knowing GeoParquet.

The Parquet implementation status page lists support for both types in Arrow C++ 21.0.0, parquet-java 1.16.0, arrow-rs 57.0.0, hyparquet 1.19.0 and DuckDB 1.4.0. Polars is read-only, and arrow-go and cudf are listed as unsupported. ClickHouse is marked unsupported, with a note that it reads the GeoParquet geo key instead. So "Parquet supports geometry" does not mean every Parquet engine handles these columns. It means the format defines them and several core libraries implement them.

Where GeoParquet 2.0 stands

GeoParquet 2.0 rebuilds the spec on the Parquet types. The latest published release is v2.0.0-rc.1, dated 19 July 2026. geoparquet.org/releases lists it as the latest release, and the repository README says the community has agreed on it but it is "still pending OGC approval". The main points of the dev spec:

  • Geometry columns must use the Parquet GEOMETRY or GEOGRAPHY type. Only WKB is allowed. The 1.1 GeoArrow-struct encodings are gone.
  • The Parquet crs property is the source of truth and may use any Parquet form. If the file also has a geo key, its crs must be inline PROJJSON describing the same CRS, or null for unknown. On the Parquet side, unknown maps to srid:0.
  • A new edges field mirrors the Parquet algorithm values (planar, spherical, vincenty and so on).
  • The geo metadata is optional for writers. A file with only native types and an EPSG:xxxx CRS is "not conformant GeoParquet 2.0", but 2.0 readers are expected to read it.

The bbox covering column has moved back and forth. The rc.1 release notes say it was removed, because native row-group statistics do the same job. Then on 7 September 2026 commit 4c9f87e ("Bring back the optional bbox covering from GeoParquet 1.1", #302) put it back on main as an option. The reason is in the distribution guide. Native geospatial statistics exist only per column chunk, which means per row group. A covering column is an ordinary struct column, so it gets a page index and lets readers prune pages inside a row group. It also gives readers that predate native statistics something to filter on. If you write against "2.0", check whether your tool targets rc.1 or the current main.

One more caution: the Cloud-Native Geospatial guide's GeoParquet page still describes the 1.0 release from 2023 and does not mention 1.1, 2.0 or the native types. Use the spec repository, not that page, for current behaviour.

Test setup: one OSM extract, six GDAL writer modes

The data is OpenStreetMap buildings for Sydney, from a BBBike PBF extract. Its header gives https://download.BBBike.org as the source and a replication timestamp of 2026-08-21 23:00 UTC. Licence: ODbL, © OpenStreetMap contributors. We kept the multipolygons layer rows with a building tag: 275,065 features, extent 150.83°E to 151.31°E and 34.06°S to 33.68°S.

The writer is GDAL 3.12.4 "Chicoutimi" from Homebrew, built against libarrow 24.0.0. ogrinfo --format Parquet lists the relevant layer creation options:

GEOMETRY_ENCODING      WKB | WKT | GEOARROW | GEOARROW_INTERLEAVED   (default WKB)
WRITE_COVERING_BBOX    AUTO | YES | NO                               (default AUTO)
USE_PARQUET_GEO_TYPES  YES | NO | ONLY                               (default NO)
EDGES                  PLANAR | SPHERICAL                            (default PLANAR)
ARROW_VERSION=24.0.0

This GDAL has no option for choosing a GeoParquet version. GDAL 3.13.3, released 18 August 2026, is newer. A diff of its Parquet driver docs against 3.12.4 shows only COVERING_BBOX_NAME and TIMESTAMP_WITH_OFFSET as new options, so nothing changes for native types.

We made two spatially sorted sources, one reprojected to GDA2020 / MGA zone 56 (EPSG:7856), the projected CRS a Sydney team would use, and one left in WGS 84. Then we wrote six files from them:

ogr2ogr -f GPKG sydney_buildings_4326.gpkg sydney.osm.pbf multipolygons \
  -where "building IS NOT NULL" -select osm_id,osm_way_id,name,building \
  -nln buildings -nlt PROMOTE_TO_MULTI
ogr2ogr src/sydney_7856_sorted.parquet sydney_buildings_4326.gpkg buildings \
  -t_srs EPSG:7856 -lco SORT_BY_BBOX=YES -lco ROW_GROUP_SIZE=10000 -lco COMPRESSION=ZSTD
ogr2ogr src/sydney_4326_sorted.parquet sydney_buildings_4326.gpkg buildings \
  -lco SORT_BY_BBOX=YES -lco ROW_GROUP_SIZE=10000 -lco COMPRESSION=ZSTD

C="-lco ROW_GROUP_SIZE=10000 -lco COMPRESSION=ZSTD -nln buildings"
ogr2ogr out/gdal_v11.parquet              src/sydney_7856_sorted.parquet $C
ogr2ogr out/gdal_native_yes.parquet       src/sydney_7856_sorted.parquet $C -lco USE_PARQUET_GEO_TYPES=YES
ogr2ogr out/gdal_native_only.parquet      src/sydney_7856_sorted.parquet $C -lco USE_PARQUET_GEO_TYPES=ONLY
ogr2ogr out/gdal_native_only_4326.parquet src/sydney_4326_sorted.parquet $C -lco USE_PARQUET_GEO_TYPES=ONLY
ogr2ogr out/gdal_geography_only.parquet   src/sydney_4326_sorted.parquet $C -lco USE_PARQUET_GEO_TYPES=ONLY -lco EDGES=SPHERICAL
ogr2ogr out/gdal_geography_yes.parquet    src/sydney_4326_sorted.parquet $C -lco USE_PARQUET_GEO_TYPES=YES -lco EDGES=SPHERICAL

(In zsh, $C does not word-split. We ran these from a bash script with an array.)

What the files look like on disk

parquet-dump-schema from Homebrew's apache-arrow package shows the difference directly. In the default 1.1 file, the geometry column has no logical type at all:

== gdal_v11.parquet
  optional binary field_id=-1 geom;
  optional group field_id=-1 geom_bbox {
    required float field_id=-1 xmin;
    required float field_id=-1 ymin;
    required float field_id=-1 xmax;
    required float field_id=-1 ymax;

With USE_PARQUET_GEO_TYPES=ONLY on the projected data, the column is typed, and GDAL puts the full PROJJSON for EPSG:7856 into the logical type's crs (shortened here):

== gdal_native_only.parquet
  optional binary field_id=-1 geom (Geometry(crs={"$schema":"https://proj.org/schemas/v0.7/projjson.schema.json",
    "type":"ProjectedCRS","name":"GDA2020 / MGA zone 56", ... "id":{"authority":"EPSG","code":7856}}));

On WGS 84 data, GDAL leaves crs empty, which by the Parquet spec means OGC:CRS84. With EDGES=SPHERICAL it switches to GEOGRAPHY:

== gdal_native_only_4326.parquet
  optional binary field_id=-1 geom (Geometry(crs=));
== gdal_geography_only.parquet
  optional binary field_id=-1 geom (Geography(crs=, algorithm=spherical));
== gdal_geography_yes.parquet
  optional binary field_id=-1 geom (Geography(crs=, algorithm=spherical));
  optional group field_id=-1 geom_bbox { required float xmin; ... }

YES mode keeps the geom_bbox covering column and, per the GDAL docs, still writes file-level GeoParquet metadata. The schema dumps confirm the covering column. We did not dump the geo JSON. The covering values are single-precision float. That is fine for a conservative filter box, but don't treat them as exact coordinates.

All files have 275,065 rows, at most 10,000 rows per row group, and ZSTD compression:

File Writer mode Geometry column bbox column Size (bytes)
gdal_v11.parquet default (NO), EPSG:7856 plain binary + geo key yes 37,100,510
gdal_native_yes.parquet YES, EPSG:7856 GEOMETRY(crs=PROJJSON) yes 37,028,662
gdal_native_only.parquet ONLY, EPSG:7856 GEOMETRY(crs=PROJJSON) no 32,564,042
gdal_native_only_4326.parquet ONLY, WGS 84 GEOMETRY(crs=) no 28,736,339
gdal_geography_yes.parquet YES + SPHERICAL, WGS 84 GEOGRAPHY(algorithm=spherical) yes 32,150,267
gdal_geography_only.parquet ONLY + SPHERICAL, WGS 84 GEOGRAPHY(algorithm=spherical) no 28,735,159

Dropping the covering column saved 4,464,620 bytes (12.0%) on the projected file and 3,415,108 bytes (10.6%) on the geographic one. YES mode costs almost the same as plain 1.1, because it keeps the covering column. These are polygon footprints. For point data the covering column is a bigger share of each row, so the saving should be larger, but we did not measure that.

What we could not test

This run did not test the readers: DuckDB 2.0.0, PyArrow, GeoPandas 1.2.0 and Polars were never installed (they failed to download in time), and we did not read the files back with ogrinfo, check row-group pruning, or time any bbox query. Closing that gap takes reading each of the six files with each of those readers, recording the geometry and CRS they report, and logging the bytes fetched for a bbox query over HTTP range requests.

Here is what their documentation says, as claims to verify rather than results. DuckDB's COPY has GEOPARQUET_VERSION with NONE, V1, V2 and BOTH, and the default is V1. Its geometry type page says only the && operator in a WHERE clause currently uses bounding-box statistics. The GeoPandas 1.2.0 changelog (28 September 2026) says GeoParquet 2.0 is supported for reading and writing. The GeoParquet tools guide says DuckDB's V2 writes native types with PROJJSON and no covering column.

Migration hazards

Some of these we saw in the files, and some come straight from the GDAL docs. Each item says which.

  • Old readers and ONLY files (documented). GDAL's driver docs say an ONLY file is fully compatible only with GDAL 3.12 or later and libarrow 21 or later. With libarrow 20, the geometry column is recognised only if its name is in GEOM_POSSIBLE_NAMES (default geometry,wkb_geometry,wkt_geometry), and the CRS is not recognised. With older libarrow, the file "cannot be opened at all".
  • Column names matter more than before (observed plus inference). GDAL kept the source name geom, which is not in that default list. Going by the documented behaviour, a libarrow-20 GDAL would not find the geometry in our ONLY files. We did not run that version. If you write ONLY, call the column geometry.
  • The CRS moves (observed). In 1.1 the CRS is in the geo JSON. In ONLY mode it exists only inside the Parquet logical type, so a tool that reads the geo key and ignores logical types gets no CRS from these files. Default WGS 84 is written as an empty crs, not as an explicit string. A reader that treats an empty string as "unknown" instead of "OGC:CRS84" would be wrong. The spec is clear on this, and implementations are a separate question.
  • YES mode labels itself 1.1 (documented). It adds native types but keeps 1.1 metadata and the covering column, which is why its size above is close to plain 1.1. The GeoParquet tools guide also says ONLY files lack the geo block and are therefore not conformant 2.0. GDAL 3.12 and 3.13 cannot produce conformant 2.0.
  • Statistics are per row group only (spec). Pruning is at row-group granularity. Over HTTP, a reader that matches one 10,000-row group fetches all of it. Sort spatially and size row groups for your access pattern, as the distribution guide recommends (50,000 to 150,000 rows for analytics, smaller for map clients).
  • GEOGRAPHY changes semantics, not just the label. EDGES=SPHERICAL tells readers that polygon edges are great-circle arcs. For building footprints a few metres across the difference is negligible. For large polygons it changes containment results, and a planar-only engine reading GEOGRAPHY may refuse the column or treat it as planar. We did not test which.

When to switch writers to native types

Based on the files above and the documented reader support:

  • Write native types, ONLY or a tool's 2.0 mode, when every consumer is on Arrow C++ 21 or later, GDAL 3.12 or later, DuckDB 1.4 or later, arrow-rs 57 or later, or parquet-java 1.16 or later. Also write native when the files go into a non-spatial engine or a table format that should see a typed geometry column instead of opaque binary. You get the CRS in the schema, per-row-group bounding boxes any implementing Parquet reader can use, and on this dataset a file about 11 to 12% smaller.
  • Keep writing GeoParquet 1.1 with a covering column when you publish to readers you don't control: QGIS installs on older GDAL, pipelines pinned to pyarrow 20 or earlier, ClickHouse, arrow-go based services, or anything that only reads the geo key. For them a native-only file can lose the CRS or fail to open.
  • Avoid GDAL's YES mode as a long-term target. It is a transition format, with both mechanisms in one file and a 1.1 label. It is useful for a release or two while you find out which consumers break.
  • Keep or re-add a covering column if your main access pattern is small bbox reads over HTTP from large row groups. Page-index pruning needs it, which is why GeoParquet main put it back.
  • Use GEOGRAPHY only if the data really has geodesic edges, for example exports from BigQuery or Snowflake geography columns. For projected engineering data like the MGA 56 file here, GEOMETRY with an explicit CRS is the correct type.

The next test should take these six files through DuckDB 2.0, GeoPandas 1.2 and PyArrow, and record which ones honour the empty-crs default and whether DuckDB's && filter skips row groups on native statistics alone, with no covering column present.

Sources