Skip to content

Arrow & storage

Exchange geometry columns through GeoArrow, store them in GeoParquet, or export cell IDs as Arrow columns. Install PyArrow support for the conversion and file examples:

uv add "gometry[arrow]"

Consumers that use the Arrow PyCapsule protocol can exchange data without PyArrow installed.

Round-trip

A GeometryArray converts to an Arrow array with to_arrow and back with gm.from_arrow, retaining CRS and coordinate epoch:

import gometry as gm

arr = gm.GeometryArray([
    gm.Point(21.0, 52.0, crs=4326),
    gm.Point(22.0, 52.0, crs=4326),
])

ga = arr.to_arrow()
print("arrow type:", ga.type)

restored = gm.from_arrow(ga)
print("round-trip:", [g.to_wkt() for g in restored])
print("crs preserved:", restored.crs.to_authority())
arrow type: extension<geoarrow.point<GeoArrowType>>
round-trip: ['POINT (21 52)', 'POINT (22 52)']
crs preserved: ('EPSG', '4326')

Geographic drawings

GeoArrow and GeoParquet use literal planar edges. Typed conversion adapts geographic branches and regions to that model: export may canonicalize longitude aliases, split parts at the seam, and add pole closures; import may subdivide long literal edges before admitting them to gometry's periodic chart. Required new vertices have finite, rounded coordinates. A split line or polygon can become its multi-part counterpart, including other rows in a homogeneous column.

Original Z/M occurrences are retained. New export seam vertices use native source-arclength interpolation; new import vertices use affine interpolation along the wire edge. Synthetic pole closures extend the incident endpoint's Z/M values. These finite drawings do not promise identical point incidence, boundary relationships, or continuous Z/M interpolation across conversion. Arrow capsules, PyArrow, and all GeoParquet encodings follow the same conversion rules. Use raw WKT/WKB when you need literal stored coordinates without this typed geographic adaptation.

GeoParquet — durable columnar storage

GeometryArray.to_geoparquet() writes geometry and aligned attributes to a file; gm.from_geoparquet reads both back. CRS, coordinate epoch, and missing rows are preserved.

import tempfile

import gometry as gm

geoms = gm.GeometryArray([
    gm.Point(21.0, 52.0, crs=4326),
    None,
    gm.Point(22.0, 52.5, crs=4326),
])
attributes = {"name": ["west", None, "east"]}

with tempfile.TemporaryDirectory() as tmp:
    path = f"{tmp}/places.parquet"
    geoms.to_geoparquet(path, attributes=attributes)
    from_file, attributes = gm.from_geoparquet(path)

print("GeoParquet:", from_file, from_file.crs.to_authority())
print("file mask:", from_file.is_missing.tolist())
print("attribute columns:", attributes.column_names)
GeoParquet: <GeometryArray[Point] len=3 missing=1 EPSG:4326> ('EPSG', '4326')
file mask: [False, True, False]
attribute columns: ['name']

Encodings

The default is WKB with GeoParquet 1.1 metadata. Choose encoding='geoarrow' for separated coordinates in a homogeneous GeoParquet 1.1 column, or encoding='geometry' for native Parquet GEOMETRY with GeoParquet 2.0 metadata. Native geometry preserves XY, XYZ, XYM and XYZM; the 1.1 encodings support XY and XYZ and reject M rather than silently dropping it. All encodings use the finite geographic drawings described above, rather than spherical or ellipsoidal geodesic curves. Nonplanar GEOGRAPHY curves require explicit conversion before import.

Spatial reads

The 1.1 writer includes a per-row bbox covering; native geometry uses Parquet's row-group geospatial statistics.

Read a spatial subset with gm.from_geoparquet(path, bbox=(xmin, ymin, xmax, ymax)). The rectangle uses the selected column's CRS, includes boundary contact, and can cross the geographic longitude seam. Coverings or statistics prune candidate reads, then exact intersection refinement selects geometry and attributes together. Missing and empty geometries do not match. Attribute filters, columns and row_groups compose with the spatial selection; requested row-group order and repetitions are retained. Geographic pruning uses conservative latitude bounds; native coordinate statistics do not exclude potentially polar areal geometries.

Streams and metadata

Native Parquet geometry can also be read without geo metadata. When several geometry columns have no declared primary, select one with geometry='name'. Native logical types, stored Arrow metadata and any GeoParquet declarations must describe the same frame.

The GeoParquet reader and writer also accept binary streams, including io.BytesIO and pyarrow.NativeFile. Readers require a seekable binary source; writers require a writable binary sink. Both leave caller-owned streams open. Text streams are unsupported.

Arrow PyCapsules without pyarrow

Geometry and GeometryArray implement the Arrow PyCapsule protocol with no optional dependency:

  • __arrow_c_schema__ → a capsule named arrow_schema
  • __arrow_c_array__ → a (schema_capsule, array_capsule) tuple named arrow_schema and arrow_array
  • __arrow_c_stream__ → a capsule named arrow_array_stream

Cell identity arrays also implement __arrow_c_array__. H3, S2, tile, H3 edge, and H3 vertex IDs use uint64; geohash cells use UTF-8 tokens. CellArray missing rows are Arrow nulls. These primitive columns do not carry the grid kind: pass the known cell type to CellArray when importing IDs, or construct an H3EdgeArray or H3VertexArray from its IDs.

A consumer that implements the Arrow C Data Interface can import the GeoArrow buffers straight from these capsules without pyarrow installed. Whether a consumer preserves GeoArrow extension metadata is version- and adapter-specific; see the compatibility matrix for deployed integration details. Install gometry[arrow] for (...).to_arrow() to return a concrete pyarrow array or to ingest an older pyarrow-only object.

The tuple is the Arrow C Data Interface boundary; it is not a geometry result that can be passed to from_arrow directly. A consumer receives the object implementing __arrow_c_array__, calls it, and owns the two capsules for the duration of the import.

import gometry as gm

values = gm.points([21.0, 22.0], [52.0, 52.5], crs=4326)
schema_capsule, array_capsule = values.__arrow_c_array__()
print('schema:', 'arrow_schema' in repr(schema_capsule))
print('array:', 'arrow_array' in repr(array_capsule))
schema: True
array: True

Cell IDs and geohash tokens

Install gometry[arrow] to use PyArrow with the cell-array protocol. Numeric cell arrays become uint64 columns; geohash cells become UTF-8 token columns. Missing CellArray entries remain Arrow nulls.

import pyarrow as pa

import gometry as gm

cell = gm.H3Cell(13.4, 52.5, resolution=7)
cells = gm.CellArray([cell, None])
ids = pa.array(cells)
restored = gm.CellArray(ids.to_pylist(), type=gm.H3Cell)
tokens = pa.array(gm.CellArray([gm.GeohashCell('u33d')]))

print("ID column:", ids.type, ids.to_pylist())
print("restored grid:", restored.grid)
print("geohash tokens:", tokens.type, tokens.to_pylist())
ID column: uint64 [608533319855898623, None]
restored grid: h3
geohash tokens: string ['u33d']

Contiguous numeric arrays and slices retain their ID buffers through the Arrow capsules. Gathered selections own a compact ID buffer, and geohash export encodes token bytes. Arrow schema requests must match the exported primitive type.

Native packed layouts vs WKB fallback

Homogeneous XY / XYZ / XYM / XYZM point, multipoint, linestring, multilinestring, polygon, and multipolygon arrays without explicit empty multipart members use the native GeoArrow coordinate layout with separated x/y/z/m children. Gometry exports may share immutable gometry-owned buffers with a consumer.

Import does not retain shared views of foreign memory. PyArrow arrays, chunked arrays, tables, record batches, and record batch readers are copied through PyArrow. Other capsule providers use the dependency-free Arrow C interface; their selected geometry buffers are copied before validation and decoding.

Mixed geometry types, mixed coordinate axes, and geometry collections fall back to GeoArrow WKB encoding to retain their structure. The PyArrow export path also uses WKB when explicit multipart member slots cannot be represented by its selected native layout; the Arrow-C nested layout retains explicit empty line and polygon slots.

Arrow C capsule trust model

The Arrow C Data Interface does not provide general buffer capacities. Providers must obey its exported structures, pointer tables, readable spans, capsule names, and release-callback rules; a provider that forges pointers or capacities is outside this boundary. Native producers must keep exported memory valid and its addressed contents quiescent until release; PyArrow objects must not be modified during from_arrow. Copying a foreign span gives gometry ownership after capture but does not synchronize a producer that writes during the copy.

Gometry copies and validates imported geometry buffers, rejecting malformed schemas, offsets, validity data, encodings, and non-finite coordinates. Resource budgets and untrusted-input limits are documented in Security & untrusted input.

CRS as PROJJSON, epoch and exact identity as gometry extensions

The GeoArrow extension metadata writes the CRS as PROJJSON — GeoArrow's recommended encoding, and the one GeoParquet 1.1 requires when columns land in a lakehouse. Gometry also writes gometry:crs_definition, a namespaced string containing the exact CRS definition stored by the producer. Its readers restore that exact identity only after verifying that it describes the standard PROJJSON CRS. External standard-only metadata continues to use the CRS decoded from PROJJSON or an authority string.

This extra identity field matters because a standardized CRS can remain spatially equivalent while its stored representation changes. For example, an EPSG:4326 geometry may be represented by a PROJJSON object after an interchange round trip; gometry's exact CRS equality distinguishes those definitions. Third-party transformations that discard gometry:crs_definition retain the standard CRS meaning but cannot preserve the producer's exact definition spelling.

The coordinate epoch is not part of the GeoArrow extension-types standard; gometry stores it as an additional JSON member in the same extension metadata blob so it can survive a columnar round-trip when both sides are gometry (or another reader that understands this extension). Other producers may omit epoch entirely. WKB/EWKB and GeoJSON still cannot carry epoch — only this gometry GeoArrow extension field and GeoParquet column metadata do.

Requested C schemas

The Arrow C array and stream exporters accept a request matching their default physical schema and reject a different layout. Some PyArrow DataType requests carry only that physical schema and omit the GeoArrow extension metadata; those exports use the requested physical type. Use GeometryArray.to_arrow() when the result must itself remain a GeoArrow extension array with CRS and epoch metadata.

Capsule consumers without pyarrow

Pass the gometry object to a consumer that supports the Arrow PyCapsule protocol and GeoArrow. To import, pass the provider object to from_arrow.

Gometry's to_polars() / from_polars() adapter instead uses a batched WKB/EWKB Binary Series so it remains independent of PyArrow; that boundary is covered in DataFrames. The lonboard adapter passes the geometry array, or an Arrow-compatible table when attributes are supplied, through the direct capsule route.

lonboard — maps in notebooks

Install gometry[viz] for interactive maps. GeometryArray._repr_html_ embeds a lonboard preview in Jupyter when lonboard is available; it uses lonboard's standalone HTML export so the same preview works wherever notebook HTML is rendered. Without lonboard, it falls back to an SVG grid preview.

import gometry as gm

arr = gm.points([21.0, 22.0], [52.0, 52.5], crs=4326)
html = arr._repr_html_()
print("notebook HTML bytes:", len(html) if html else 0)
print("explore available:", callable(gm.explore))
notebook HTML bytes: 4636443
explore available: True

gm.explore hands lonboard the geometry through its Arrow-compatible capsule route. Inputs need a CRS and finite bounds. By default it presents a map where points, lines, and polygons remain distinct and features are inspectable on hover. Pass lonboard's scatterplot_kwargs, path_kwargs, polygon_kwargs, or map_kwargs to tailor individual settings.

Reprojection for display

lonboard expects WGS 84 longitude/latitude display coordinates. Arrays whose CRS is not equivalent to EPSG:4326 / OGC:CRS84 (including non-WGS84 geographic frames such as NAD83, and all projected frames) should be reprojected with to_crs(4326) before display. The viz helper normalizes non-WGS84 geographic CRS the same way as projected ones.

See also