GeoParquet and the cloud-native geospatial stack

Pull-quote: “A shapefile makes you read every attribute of every feature to answer a question about one column. That is the whole argument for columnar geometry.”
GeoParquet is a specification for storing vector geometry inside Apache Parquet files, and version 1.1.0 standardises one thing precisely: a JSON metadata block under a geo key in the Parquet footer, which tells any reader what the geometry column contains and how to interpret it. Everything else people credit to GeoParquet, the compression, the column pruning, the row group statistics, comes from Parquet itself. Keeping that split straight is what lets you reason about performance instead of guessing at it.
This post is for the engineer or architect whose spatial data has outgrown shapefiles and a single PostGIS instance. It covers what the specification requires, why columnar layout changes which queries are cheap, how the format compares with the three alternatives it is weighed against, how to partition so most of your data never gets read, and what changed when Parquet added native geospatial types. The examples come from aviation and freight, where Zorost does most of its spatial engineering: position reports, runway geometry, airspace polygons, telematics pings, shipment legs, terminal boundaries.
What you will be able to do
- State what GeoParquet 1.1.0 requires in a file, and check any file against it from the command line.
- Explain why columnar layout makes some spatial queries far cheaper and leaves others unchanged.
- Choose between GeoParquet, GeoPackage, GeoJSON, and shapefile by access pattern rather than habit.
- Partition and order a dataset so a bounded query reads a small fraction of the files.
- Describe how GeoParquet 1.1.0, the native Parquet
GEOMETRYandGEOGRAPHYtypes, and GeoParquet 2.0 relate. - Query spatial tables on a lakehouse without running a separate spatial database.
What does GeoParquet actually standardise?
GeoParquet standardises geometry encoding and geometry metadata inside an otherwise ordinary Parquet file, and nothing else. A GeoParquet file must include a geo key in the Parquet file key-value metadata whose value is a JSON string carrying three required fields: version, primary_column naming the default geometry column, and columns, an object describing every geometry column present.
Per column, the specification requires an encoding and a geometry_types array, and permits crs, orientation, edges, bbox, epoch, and covering. The encoding is either "WKB", meaning ISO Well Known Binary in a BYTE_ARRAY column, or one of the single geometry type encodings borrowed from GeoArrow such as "point" or "multipolygon". The crs is a PROJJSON object; if the key is absent, coordinates must be longitude then latitude on the WGS84 datum, which the specification calls OGC:CRS84, and setting crs to null instead means the reference system is unknown. The edges field is "planar" or "spherical" and defaults to planar, deciding whether the line between two vertices is a straight cartesian segment or a geodesic.
Two structural rules reject files rather than degrade them. Geometry columns must sit at the root of the schema, so no nesting inside a struct or list. Repetition must be required or optional, never repeated. Several geometry columns with different names are allowed, which is how a table carries both a flight track linestring and its origin point.
Two things the specification deliberately omits: a spatial index and a file extension of its own. There is no R-tree in GeoParquet. Acceleration comes from the optional covering field, which names four root-level bounding box columns so a reader can skip row groups using ordinary Parquet statistics. On naming it is explicit: use .parquet, and .geoparquet should not be used.

Version 1.1.0 is the current stable release, published at geoparquet.org/releases/v1.1.0; the repository’s main branch carries a 1.2.0 development version. GeoParquet is an incubating Open Geospatial Consortium standard rather than an approved one: OGC formed a GeoParquet Standards Working Group in January 2025, and the candidate standard sits in draft as OGC document 24-013. Write “incubating OGC standard” in anything a procurement officer will read.
Checking a file takes one line. The gpq command line tool describes and validates GeoParquet, or read the key directly:
python -c "import pyarrow.parquet as pq, json; \
print(json.loads(pq.read_schema('flights.parquet').metadata[b'geo'])['version'])"
You should see 1.1.0. A KeyError on b'geo' means the file is plain Parquet with a binary column in it, and every reader will treat that geometry as an opaque blob.
Why does columnar storage matter for geometry?
Columnar storage matters because most spatial questions touch a few columns out of many, and a row-oriented format makes you read all of them. A shapefile or GeoJSON feature collection stores each feature as a unit, so counting how many position reports fell inside one airspace sector last Tuesday means parsing every attribute of every feature to reach the two you need.
Three mechanisms do the work and they compound. Column pruning fetches only the columns the query names, which on a wide telematics table is often a tenth of the bytes. Predicate pushdown evaluates the filter against per row group minimum and maximum statistics in the footer and skips whole row groups that cannot satisfy it. Encoding and compression run per column, which beats compressing mixed-type rows, because a column of timestamps has low entropy and a row does not.
Geometry is the awkward case, because a WKB blob is high-entropy binary and compresses poorly. That is what covering is for. Per-row bounding box columns beside the geometry give the planner four cheap numeric columns with useful statistics, so a spatial filter is answered approximately in the footer before any geometry is decoded. Overture Maps does this in every release: their files carry a bbox struct with xmin, xmax, ymin, and ymax specifically so consumers can write efficient spatial queries against cloud storage, as their data repository documents. Filter on the bounding box first, then run the exact predicate on the survivors.
One honest limit: none of this helps a query with no selective predicate. Reprojecting every geometry in the dataset gains you compression and parallelism and nothing else.
How does GeoParquet compare with shapefile, GeoJSON, and GeoPackage?
Each format optimises for a different moment in the life of spatial data, and only one was designed for a query engine reading part of a large dataset over object storage.
| Format | Designed for | Partial read over HTTP | Where it still wins |
|---|---|---|---|
| Shapefile | Desktop GIS interchange | No, multi-file and row-oriented | Legacy tooling that accepts nothing else |
| GeoJSON | Web APIs and human inspection | No, one JSON document | Small results, debugging, browser rendering |
| GeoPackage | One portable indexed file | No, SQLite expects a local file | Field work, offline devices, single handoff |
| GeoParquet | Analytics at scale on object storage | Yes, footer plus row group ranges | Large datasets read by many engines |

The comparison people get wrong is GeoPackage against GeoParquet. GeoPackage is a SQLite database, so it carries a real R-tree index and supports random access and updates, and it expects a local file and a single writer. GeoParquet is immutable, splittable, and designed for parallel reads from object storage. If the deliverable is a file someone opens in QGIS, GeoPackage is usually right. If it is a table that a Spark job, a DuckDB session, and a warehouse all read at once, it is GeoParquet.
Shapefile deserves one specific warning. Its 10-character field name limit silently truncates and collides column names on export, so a round trip can turn scheduled_departure_utc and scheduled_departure_local into the same thing. That is a corruption path, not an inconvenience.
How do you partition spatial data so queries skip most of it?
Partition on the dimension your queries filter on hardest, which for movement data is time first and space second. Position reports and telematics pings arrive as an unbounded stream, and nearly every question starts with a date range. A layout of event_date=2026-07-01/region=NA/part-00000.parquet eliminates directories before the engine opens a footer.
Within a partition, ordering is what makes bounding box statistics useful. Rows written in arrival order scatter geographically, so every row group’s box covers half a continent and none can be skipped. Sorting by a space-filling curve puts nearby geometries in the same row group and shrinks those boxes to something selective. Sort by H3 cell at a coarse resolution, or use Hilbert curve ordering, which the GeoParquet Tools utility linked from geoparquet.org does through DuckDB. Either way write the covering columns, because an ordering with no statistics to expose is wasted work.
Size files deliberately. Hundreds of thousands of small files cost more in listing and footer reads than they save in skipping, and one enormous file cannot be split across workers. Target the low hundreds of megabytes: for a freight telematics table, one partition per day per region, not one file per vehicle.
Overture’s transportation theme shows the technique in one query, since it distributes globally as GeoParquet with the bounding box struct:
SELECT count(*)
FROM read_parquet('s3://overturemaps-us-west-2/release/*/theme=transportation/*/*.parquet')
WHERE bbox.xmin > -77.2 AND bbox.xmax < -76.8
AND bbox.ymin > 38.7 AND bbox.ymax < 39.1;
The filter never decodes a geometry. It runs against four numeric columns whose statistics live in the footer, so the engine reads a handful of row groups out of a global dataset, and the same technique transfers to your own lane geometry or sector polygons.
What changed when Parquet itself added GEOMETRY and GEOGRAPHY?
Apache Parquet added native GEOMETRY and GEOGRAPHY logical types to the format specification, moving geometry from a labelled binary column into the type system. The Parquet geospatial type specification defines both as WKB-encoded and distinguishes them by edge interpolation: GEOMETRY uses linear planar edges, GEOGRAPHY carries an explicit algorithm such as SPHERICAL, VINCENTY, THOMAS, ANDOYER, or KARNEY. It also defines GeospatialStatistics, a per column chunk struct holding a bounding box with optional Z and M dimensions plus the geometry types present.
That last part is the substantive change. Under GeoParquet 1.1.0 you construct spatial statistics yourself by writing extra columns and declaring them in covering. Under native types the writer produces them as part of the format, so a planner that knows nothing about the geo key can still skip row groups spatially. Apache Iceberg version 3 adopted geometry and geography types on the same basis.
The published direction, called GeoParquet 2.0 by its maintainers, is to keep one geometry type, the Parquet one, drop the GeoArrow-based encodings, and remove bbox because the format now supplies the statistics that field existed to provide. The geo key is expected to keep being written through the transition.
What to do today is unglamorous: write WKB-encoded GeoParquet 1.1.0 with a covering bounding box, because that is what the widest set of readers understands. Confirm your engines write native types before relying on them, and check the reader at the other end, because a logical type an older reader does not recognise is a support ticket rather than a graceful fallback.
How does GeoParquet land on a lakehouse next to everything else?
It lands as an ordinary table, which is the point: spatial data stops being a separate system with its own governance, backups, and access requests. Once geometry is in Parquet it is in Delta Lake, registered in a catalog, and a spatial join against a non-spatial table is one query rather than an export.
On Databricks, ST geospatial functions apply to Databricks SQL and Databricks Runtime 17.1 and above, with GEOMETRY support generally available and GEOGRAPHY support in Public Preview, per the ST geospatial functions reference. H3 functions have shipped since Databricks Runtime 11.2 and need no install, so hexagonal aggregation of movement data is a GROUP BY:
SELECT h3_longlatash3(lon, lat, 8) AS cell,
count(*) AS pings
FROM telematics_positions
WHERE event_date = DATE '2026-07-01'
GROUP BY cell;

The layering follows the same shape as any other estate, which is the argument in data architecture patterns and choosing by fit. Raw position reports land append-only. A normalisation layer does the expensive work once: reprojection to one reference system, geometry validity repair, map matching of pings onto network segments, and the H3 or bounding box columns later queries depend on. Business-facing tables hold what people query, such as sector crossings per hour or dwell time per terminal. Governance rides along, so an airspace geometry with a distribution restriction is protected by catalog permissions rather than a folder convention.
Plan the join to a reference network early. Overture Maps assigns stable Global Entity Reference System identifiers to transportation segments and connectors, so matching track data to a GERS identifier once turns every later join into a column equality instead of a spatial predicate, as their GERS documentation describes.
Where this goes wrong
No coordinate reference system, or the wrong one. The symptom is a spatial join returning nothing, or distances wrong by an unexplained factor. The cause is usually a file written with crs absent, which the specification defines as longitude then latitude on WGS84, when the data is actually projected. Set crs explicitly with PROJJSON and reproject once in the normalisation layer.
Geometry nested inside a struct. The symptom is a file some readers open and others reject. The cause is an export that wrapped geometry in a nested type, which the specification forbids. Flatten before writing.
No covering, so every spatial filter is a full scan. The symptom is that a query bounded to one city takes as long as one over a continent. The cause is that nothing gives the planner numeric statistics to skip on. Write the four bounding box columns, declare them in covering, and sort rows spatially inside each partition.
Latitude and longitude transposed. The symptom is data plotting in the wrong hemisphere, usually the Indian Ocean. The cause is a reader honouring the axis order declared in an EPSG:4326 definition. GeoParquet overrides that: WKB axis order is always x then y. Assert it in a test rather than discovering it on a map.
Treating GeoParquet as an update target. The symptom is rewriting a large partition to correct three rows. The cause is expecting database behaviour from an immutable file format. Put the table under Delta Lake for transactional updates and compaction, and keep raw GeoParquet for interchange.
Common questions
Is GeoParquet an official OGC standard yet?
Not yet. GeoParquet describes itself as an incubating Open Geospatial Consortium standard. OGC formed a GeoParquet Standards Working Group in January 2025 and the candidate standard is published in draft as OGC document 24-013. Released versions do not change, so amendments needed for approval would arrive under a new version number.
Do I need a special file extension for GeoParquet?
No. The specification recommends .parquet and states that .geoparquet should not be used, because compatibility with existing Parquet tooling matters more than self-description. If a media type is used it must be application/vnd.apache.parquet. Readers detect GeoParquet by looking for the geo key in the file metadata.
Does GeoParquet include a spatial index?
No. There is no R-tree or quadtree in the specification. It provides the covering field, which names per-row bounding box columns so ordinary Parquet row group statistics can skip data spatially. With rows sorted by a space-filling curve inside each partition, that delivers most of the practical benefit of an index for read-only analytical access.
Should I write WKB or the native GeoArrow encodings?
Write WKB unless you have measured a reason not to. It is the portable choice and the one most readers support. The single geometry type encodings can perform better and produce more useful row group statistics, but they require every reader in the pipeline to support them, and GeoParquet 2.0 plans to drop them in favour of the native Parquet type.
Can I keep using PostGIS if I adopt GeoParquet?
Yes, and most teams should keep both. PostGIS remains right for transactional spatial work: editing features, serving an application, enforcing constraints at write time. GeoParquet is the analytical copy that many engines read at once. The usual arrangement is PostGIS as system of record for curated reference geometry, and lakehouse tables for high-volume movement data.
Next steps
Take one spatial dataset you already have, write it as GeoParquet 1.1.0 with a covering bounding box, sort it by H3 cell inside each daily partition, and time the same bounded query before and after. That measurement settles more architecture arguments than a week of discussion.
Then read geospatial AI for transportation and mobility, which picks up where storage stops and covers routable networks and pretrained spatial models. If your problem is upstream of format choice, bad pipelines quietly destroy good data covers the contracts and freshness checks that stop a bad reprojection reaching a map. More engineering writing sits in the Signals index.
Zorost builds these spatial layers on client lakehouses as a trusted Databricks partner, where geometry arrives as a firehose of position reports rather than a tidy extract, and the normalisation layer earns its cost on the first join.
