GeoPackage vs GeoParquet vs Shapefile for Farm Data

TL;DR: Use GeoParquet as the analytical store for dense yield and sampling data, GeoPackage as the multi-layer desktop-editing container, and Shapefile only at the final export to a controller or FMIS — never as the store of record for large point datasets.

Why This Choice Matters

Picking a vector format for a farm pipeline is not a matter of taste — the wrong pick shows up as slow scans, bloated storage, corrupted attribute names, or a file that a combine’s display refuses to import. A season of 1 Hz yield logging is hundreds of thousands of points per field; kept as Shapefile, that data reads slowly, approaches the 2 GB component ceiling, and truncates any attribute name past 10 characters. Kept as GeoParquet, the same data is several times smaller and a field-clipped query reads a fraction of the file. But swing too far and store your controller export as GeoParquet, and the machine simply cannot open it. This guide resolves the three-way trade-off with a concrete matrix and a benchmark you can run on your own data.

For the full structural background on each format, this guide sits under Geospatial File Formats for Farm Data Pipelines.

The Comparison, at a Glance

Format comparison matrix for farm vector data A grid rating Shapefile, GeoPackage, and GeoParquet on analytical read speed, file size, multi-layer support, cloud predicate pushdown, and equipment compatibility, showing Shapefile strong only on equipment compatibility and GeoParquet strongest on analytical read and size. Shapefile GeoPackage GeoParquet Analytical read Small file size Multi-layer Predicate pushdown Equipment / FMIS strong partial weak
Criterion Shapefile GeoPackage GeoParquet
Read speed (full layer) Moderate Moderate Fast
Read speed (subset of columns / spatial window) Slow (full scan) Slow–moderate Fast (pushdown)
Write speed Fast Moderate Fast
Typical file size (dense points) Largest Smaller Smallest (columnar + zstd)
Multi-layer in one file No (one layer per file set) Yes (many named layers) No (one layer per file)
Cloud / predicate pushdown No Limited Yes (row-group stats + bbox)
Random-access editing No Yes (SQL) No (rewrite to change)
Attribute name length 10 chars (hard cap) Unlimited Unlimited
Component / size ceiling 2 GB per .shp/.dbf Practically none Practically none
QGIS / desktop GIS native Yes Yes (first-class) Newer support, improving
Equipment controller / FMIS import Yes (de facto standard) Rare No

Per-Use-Case Guidance

Field boundaries and management zones you edit by hand → GeoPackage. These are low-volume, high-value layers that agronomists open and adjust in QGIS. A single .gpkg holds boundaries, zones, and sampling points as named layers, with full-length column names and embedded CRS. Editing is transactional through SQL, and there is no sidecar to lose. Boundary cleanup that precedes this storage is covered in field boundary extraction with GeoPandas.

The choice is decided by the consumer, not by preference A decision diagram for format choice. If farm equipment will read the file directly, compatibility decides and the export is a shapefile or controller format generated from a canonical copy. Otherwise GeoPackage suits offline and edge work while GeoParquet suits analytical queries. Choosing a format for a farm dataset will equipment read the file directly? yes Shapefile, or the controller's own format compatibility wins; convert from your canonical copy at export time, never store it this way no GeoPackage for edge and offline work, GeoParquet for analysis one file with its CRS inside, or a columnar layout with per-row-group statistics

Dense yield-monitor points and multi-season archives → GeoParquet. This is where columnar storage pays off. Reading yield_bu_ac and geometry for one field out of a statewide archive touches a small fraction of the file thanks to column pruning and row-group skipping. Storage drops several fold versus Shapefile. This is also the safe home for data that would breach the Shapefile 2 GB ceiling.

The final prescription handed to a machine → Shapefile or ISOXML. Whatever the internal store, the export at the equipment boundary is Shapefile or ISOXML because that is what John Deere GreenStar, Trimble, Raven, and CNH controllers ingest. Rename long attribute names to short unique tokens first, and set UTF-8 encoding so operator notes survive. Validating that export against equipment expectations is the subject of shapefile validation for farm equipment.

Prerequisites

Only pyarrow differs from the parent guide’s environment:

TEXT
geopandas==0.14.4
pyogrio==0.9.0
pyarrow==16.1.0
BASH
pip install geopandas==0.14.4 pyogrio==0.9.0 pyarrow==16.1.0

Step-by-Step: Benchmark the Three on Your Own Data

The comparison table is directional; the only authority is your own field-season on your own hardware. This script writes the same GeoDataFrame to all three formats, then times a realistic filtered read and records file size.

Step 1 — Load a representative layer and write all three formats

PYTHON
import time
import geopandas as gpd
from pathlib import Path

# A dense point layer is the fairest stress test (yield monitor logs)
src = gpd.read_file("yield_2025.gpkg", layer="points", engine="pyogrio")
assert src.crs is not None and src.crs.is_projected, "Project to UTM before benchmarking"
print(f"Rows: {len(src):,}  Columns: {len(src.columns)}  CRS: EPSG:{src.crs.to_epsg()}")

src.to_file("bench.shp", driver="ESRI Shapefile", engine="pyogrio", encoding="UTF-8")
src.to_file("bench.gpkg", layer="points", driver="GPKG", engine="pyogrio")
src.to_parquet("bench.parquet", compression="zstd", write_covering_bbox=True)

Step 2 — Time a column-subset read and record size

PYTHON
def file_size_mb(pattern: str) -> float:
    return sum(p.stat().st_size for p in Path().glob(pattern)) / 1e6

def timed_read(fn) -> tuple[int, float]:
    t0 = time.perf_counter()
    out = fn()
    return len(out), time.perf_counter() - t0

# Realistic analytical read: only geometry + one measured column
COLS = ["geometry", "yield_bu_ac"]

reads = {
    "Shapefile":  lambda: gpd.read_file("bench.shp", columns=COLS, engine="pyogrio"),
    "GeoPackage": lambda: gpd.read_file("bench.gpkg", columns=COLS, engine="pyogrio"),
    "GeoParquet": lambda: gpd.read_parquet("bench.parquet", columns=COLS),
}
sizes = {
    "Shapefile":  file_size_mb("bench.shp") + file_size_mb("bench.dbf")
                  + file_size_mb("bench.shx") + file_size_mb("bench.prj"),
    "GeoPackage": file_size_mb("bench.gpkg"),
    "GeoParquet": file_size_mb("bench.parquet"),
}

print(f"{'Format':<12}{'Rows':>10}{'Read s':>10}{'Size MB':>10}")
for name, fn in reads.items():
    n, secs = timed_read(fn)
    print(f"{name:<12}{n:>10,}{secs:>10.3f}{sizes[name]:>10.1f}")

# Verification: every format must return the same row count and CRS
counts = {name: len(fn()) for name, fn in reads.items()}
assert len(set(counts.values())) == 1, f"Row counts diverged: {counts}"
print("All three formats agree on row count:", next(iter(counts.values())))

On a typical several-hundred-thousand-point yield layer, expect GeoParquet to be the smallest file and the fastest column-subset read, GeoPackage to sit in the middle, and the Shapefile to be the largest on disk. The exact ratios depend on how repetitive your attribute columns are — heavy repetition (season, crop, zone codes) favours GeoParquet’s dictionary encoding most.

Gotchas & Edge Cases

  • Comparing full reads hides GeoParquet’s advantage. If you benchmark read_file without a columns argument, all three walk every field and the columnar win disappears. The realistic farm workload reads a few columns or a spatial window — always benchmark that, not a naive full read.
  • pyarrow version drives GeoParquet features. write_covering_bbox and newer GeoParquet metadata need recent pyarrow and geopandas. On older stacks the call silently omits the bbox column, and predicate pushdown on spatial filters quietly does nothing.
  • GeoPackage columns= still scans rows. Column selection reduces what is materialised, but SQLite is row-oriented, so it does not skip row groups the way Parquet does. Do not expect GeoParquet-class scan performance from a GPKG.
  • Reading GeoParquet with plain pandas loses geometry and CRS. Use geopandas.read_parquet, never pandas.read_parquet, or the WKB column comes back as opaque bytes with no CRS attached.
Where the columnar layout actually pays Bars comparing a million farm boundaries stored three ways, relative to shapefile as the baseline. GeoPackage is slightly smaller, GeoParquet is about a third the size, and a filtered GeoParquet read touches only a fraction because whole row groups are skipped without being decoded. Shapefile, 1 M boundaries baseline size and read time GeoPackage, same data ≈ 0.8× size, similar read GeoParquet, full read ≈ 0.34× size, faster read GeoParquet, one season filtered row groups skipped, not decoded Relative to shapefile at 100. The last bar is the one that matters: filtering is where columnar storage stops being a tie.

Parent Guide

This guide is part of Geospatial File Formats for Farm Data Pipelines — see there for the full pipeline context, the parameter reference, and the raster (GeoTIFF/COG) side of the format decision.