GeoParquet vs Cloud Optimized GeoTIFF for Mesh Distribution
Choosing the wire format for a spatial data product is a contract decision, not a storage convenience: it fixes how consumers read, filter, and reproject the data across every domain boundary. This page compares GeoParquet against Cloud Optimized GeoTIFF (COG) as the two canonical distribution formats in a mesh, and belongs to Metadata Cataloging for Raster/Vector under Geospatial Data Mesh Fundamentals. The short rule: distribute discrete features and attribute tables as GeoParquet with columnar predicate pushdown, and distribute continuous raster surfaces as COG with HTTP range reads and internal overviews. The catalog entry that advertises either format follows the same discipline set out in Best Practices for Spatial Metadata Catalogs.
Both formats are cloud-native and support partial reads over object storage, so the decision turns on the data model, not on whether a file “opens in the cloud.” The matrix below draws the line dimension by dimension.
Figure — A six-dimension comparison matrix contrasting GeoParquet and Cloud Optimized GeoTIFF for mesh distribution.
Prerequisites
| Requirement | Version / assumption | Notes |
|---|---|---|
gdal (CLI) |
>= 3.6 | ogrinfo, ogr2ogr, gdalinfo; GeoParquet driver requires GDAL built with Arrow/Parquet |
rio-cogeo |
>= 5.0 | rio cogeo validate / rio cogeo create for COG conformance |
pyarrow |
>= 14.0 | Reads GeoParquet key-value metadata and row-group statistics |
curl |
any | HTTP range reads against object storage for COG header probing |
| CRS assumption | Vector products in EPSG:4326; raster products may carry EPSG:32633 and reproject only at egress |
GeoParquet stores CRS as PROJJSON; COG stores it in GeoTIFF SRS tags |
| Access role | geo-catalog-reader (S3 GET + range) |
Range reads must be permitted by the bucket policy |
| Env | GDAL_HTTP_MULTIRANGE=YES, VSI_CACHE=TRUE |
Enables efficient multi-range fetches for /vsicurl/ |
Step-by-Step Implementation
1. Inspect a GeoParquet product before advertising it
Confirm the geometry column, feature count, CRS, and that the file carries the geo metadata GeoParquet requires. ogrinfo reads the columnar structure without materializing every row:
ogrinfo -so -al roads_v1.2.0.parquet
# Layer name: roads
# Geometry: Line String
# Feature Count: 184203
# Extent: (-122.52, 37.70) - (-122.34, 37.83)
# SRS WKT: GEOGCRS["WGS 84", ... ID["EPSG",4326]]
Verify the columnar metadata and row-group count with pyarrow so you know predicate pushdown will actually prune:
python3 -c "import pyarrow.parquet as pq; f=pq.ParquetFile('roads_v1.2.0.parquet'); \
print('row_groups=', f.num_row_groups); \
print('has_geo=', b'geo' in f.metadata.metadata)"
# expect: row_groups= 12
# expect: has_geo= True
2. Inspect and validate a COG product
For raster, confirm the file is a valid COG — internal tiling plus overviews — because a plain GeoTIFF served over HTTP forces whole-file reads. gdalinfo reports the block layout and overview levels:
gdalinfo dem_v1.2.0.tif | grep -E "Block|Overviews|EPSG"
# Band 1 Block=512x512 Type=Float32
# Overviews: 4096x4096, 2048x2048, 1024x1024, 512x512
Then run the conformance check that the format actually mandates:
rio cogeo validate dem_v1.2.0.tif
# dem_v1.2.0.tif is a valid cloud optimized GeoTIFF
3. Prove the range-read access pattern for COG
The property that makes COG mesh-ready is the HTTP range read: a consumer fetches the header and a single tile without downloading the file. Fetch just the leading bytes that hold the IFD and tile index:
curl -s -r 0-16383 \
https://cdn.geo-mesh.example/dem_v1.2.0.tif -o cog_header.bin
# Server must answer 206 Partial Content, not 200 OK
curl -s -o /dev/null -w "%{http_code} %{size_download}\n" \
-r 0-16383 https://cdn.geo-mesh.example/dem_v1.2.0.tif
# expect: 206 16384
Confirm GDAL can then read a window remotely over /vsicurl/ without pulling the whole raster:
gdalinfo /vsicurl/https://cdn.geo-mesh.example/dem_v1.2.0.tif | grep "Size is"
# Size is 8192, 8192 (read from the header alone, no full download)
4. Apply the decision and record the format in the catalog
Use the table below to pick the format, then declare it in the STAC asset type so consumers negotiate the right reader. This is the same catalog envelope described in Best Practices for Spatial Metadata Catalogs.
| If the product is… | Access need | Choose | STAC asset type |
|---|---|---|---|
| Discrete features (roads, parcels) | Attribute filter + spatial bbox | GeoParquet | application/vnd.apache.parquet |
| Wide attribute tables joined by key | Column projection, aggregation | GeoParquet | application/vnd.apache.parquet |
| Continuous surface (DEM, NDVI) | Windowed pixel reads, zoom | COG | image/tiff; application=geotiff; profile=cloud-optimized |
| Multispectral imagery scenes | Overview pyramids for preview | COG | image/tiff; application=geotiff; profile=cloud-optimized |
| Mixed feature + raster product | Both | Publish two assets, one per format | both of the above |
Because CRS travels differently in each format — PROJJSON for GeoParquet, embedded SRS tags for COG — pin the projection at the product boundary rather than reprojecting on read, as covered in Standardizing on EPSG:4326 Across Domains.
Configuration Reference
| Format | Key setting | Recommended value | Why it matters |
|---|---|---|---|
| GeoParquet | ROW_GROUP_SIZE |
50000–150000 rows | Sets predicate-pushdown granularity; too small inflates metadata, too large defeats pruning |
| GeoParquet | COMPRESSION |
ZSTD |
Best ratio/speed balance for columnar geometry |
| GeoParquet | GEOMETRY_ENCODING |
WKB |
Widest cross-tool interoperability |
| GeoParquet | partition keys | Hive dirs on region= / date= |
Enables directory-level pruning before row groups |
| COG | BLOCKSIZE |
512 |
Tile size for range reads; 256–512 balances request count and payload |
| COG | OVERVIEW_RESAMPLING |
average (continuous) / nearest (categorical) |
Wrong choice corrupts previews of classified rasters |
| COG | COMPRESS |
DEFLATE or ZSTD |
Keeps tiles independently decodable for range reads |
| COG | PREDICTOR |
2 (int) / 3 (float) |
Improves compression without breaking tiling |
Common Failure Modes & Fixes
Symptom: curl -r against the COG returns 200 OK and the full file size, not 206.
Root cause: the object store or CDN in front of it does not honor Range headers, so every “windowed” read downloads the whole raster.
Fix: enable range support on the bucket/CDN and re-test with the step 3 command; confirm the response is 206 Partial Content before advertising the asset.
Symptom: rio cogeo validate reports “not a valid cloud optimized GeoTIFF.”
Root cause: the file has no internal tiling or no overviews — it is a striped GeoTIFF that happens to sit in object storage.
Fix: rewrite it with rio cogeo create -p deflate --overview-resampling average in.tif out.tif and re-validate.
Symptom: GeoParquet reads scan every row group despite a bbox filter.
Root cause: the writer produced a single giant row group, or geometry bbox statistics were not written, so there is nothing to prune on.
Fix: rewrite with a bounded ROW_GROUP_SIZE and a spatially sorted order (e.g. Hilbert curve) so row-group bboxes are tight, then confirm num_row_groups > 1.
Symptom: consumers report the vector product is in the wrong projection.
Root cause: the geo PROJJSON metadata was stripped during a copy, so readers fall back to assuming EPSG:4326.
Fix: rewrite with ogr2ogr -f Parquet -a_srs EPSG:4326 out.parquet in.parquet and verify ogrinfo -so reports the SRS WKT.
FAQ
Can a single product be published as both GeoParquet and COG?
Yes, when the product genuinely has both a vector and a raster representation — for example a flood-risk product with a polygon hazard layer and a depth-grid raster. Publish two assets under one catalog item, each with its own type, so a consumer selects the representation that fits its access pattern rather than converting on the fly.
Why not just serve GeoJSON or a plain GeoTIFF over HTTP?
Neither supports partial reads at scale. GeoJSON has no row groups, so any filter reads the whole file; a striped GeoTIFF has no internal tiling, so any window read pulls the whole raster. GeoParquet and COG exist precisely to make bbox and column filters cheap over object storage.
How does CRS handling differ between the two formats in practice?
GeoParquet records the CRS as PROJJSON inside the geo file metadata and, per the GeoParquet spec, defaults to EPSG:4326 when absent. COG embeds the CRS in GeoTIFF SRS tags read by GDAL. Because the carrier differs, validate CRS with ogrinfo -so for GeoParquet and gdalinfo for COG rather than assuming a shared mechanism.
Does GeoParquet replace PostGIS for a vector domain?
No. GeoParquet is a distribution and analytics format for immutable, versioned snapshots read by many consumers; PostGIS remains the transactional engine behind a domain. Export a versioned GeoParquet product from PostGIS at publish time, and let consumers query the snapshot without loading the operational database.
Related
- Metadata Cataloging for Raster/Vector — the parent concern that advertises whichever format you choose.
- Best Practices for Spatial Metadata Catalogs — the catalog envelope that carries the asset
type. - Standardizing on EPSG:4326 Across Domains — how CRS is pinned regardless of format carrier.
- Implementing Product Thinking for Satellite Imagery Datasets — a COG-centric raster product pipeline end to end.
- Data Mesh vs Traditional GIS Architecture — why format choice is a consumer contract, not a storage detail.