Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Marquet: Geospatial Vector Data in HDF5

HDF Group

1Introduction

Geospatial vector data — points, lines, and polygons, each accompanied by a row of descriptive attributes — is among the most widely exchanged kinds of tabular data. Over the past few years the columnar data ecosystem has settled on GeoParquet as its standard for such data. GeoParquet specifies how geometries are encoded in the columns of an Apache Parquet file and which metadata a reader needs in order to interpret them: the coordinate reference system, the kinds of geometry present, how the edges between vertices are to be understood, and the spatial extent of the data. Since version 2.11 of the Parquet format, released in 2025, Parquet itself defines native GEOMETRY and GEOGRAPHY column types with per-row-group spatial statistics, and GeoParquet 2.0 builds directly on them.

HEP002 proposes Marquet, a convention that brings the same capability to HDF5. Marquet is not a new table format. It is a thin geospatial layer on top of H5Col (HEP001), the column-oriented table convention for HDF5, and it reuses H5Col for everything that is not specifically geospatial: the table group and its row count, per-column chunking and filtering, missing values, list columns, search indexes, and the rules that keep a table consistent while it is being appended to. What Marquet adds is the notion of a geometry column, the ways a geometry column may be stored, the attributes that describe its geometries, two kinds of spatial search index, and a precise mapping to and from GeoParquet.

1.1Why geospatial vector data belongs in HDF5

A great deal of the world’s gridded geospatial data already lives in HDF5: satellite swaths and granules, numerical weather and climate model output, radar volumes, and lidar products. The vector features that accompany such data — the stations that measured it, the storm tracks and fire perimeters derived from it, the field boundaries and flight lines that organize it — are usually kept in separate files, in formats such as shapefiles, GeoPackage, or GeoParquet. Keeping both in one HDF5 file removes that seam. The features and the arrays then travel together as one unit, share one access-control and transfer story, and are read through one API, and HDF5 object and region references can tie each feature to the slab of an array that it describes without duplicating any bytes. Scientists who work in C, Fortran, Java, MATLAB, IDL, or Julia can reach the features through the same HDF5 library they already use for the arrays.

Marquet records its metadata with HDF5’s own datatypes. Closed vocabularies, such as the list of geometry types or the edge interpretations, are HDF5 enumerated datatypes. Bounding boxes are compound datatypes with named fields. Links between columns are object references. An unknown coordinate reference system is an attribute with a null dataspace. And point geometries may be stored either as WKB or as floating-point coordinates.

1.2The name Marquet

The name Marquet (pronounced mar-kay or mar-ket) is a deliberate nod to the columnar data ecosystem, and specifically to Apache Parquet and GeoParquet, while giving spatial data in HDF5 an identity of its own. It comes from woodworking. Parquetry is the craft of laying uniform geometric blocks in repeating grid patterns, which makes it a good metaphor for ordinary tabular data: predictable rows and columns spread across a flat surface. Marquetry, by contrast, is the craft of cutting intricate shapes, organic curves, and whole pictures from veneer and then inlaying them into a larger, solid, three-dimensional structure such as a cabinet.

That distinction mirrors the step from flat files to HDF5. Geospatial data is inherently intricate: multipolygons, long winding linestrings, and dense coordinate sequences are the digital equivalent of marquetry’s custom-cut pieces. Just as marquetry relies on a solid piece of furniture to hold its designs, this specification inlays those geometries into the hierarchical, chunked storage of an HDF5 file. The layout underneath remains columnar, preserving the speed and efficiency that made Parquet successful, so Marquet brings the columnar practice of modern data engineering into the nested hierarchy of HDF5 and gives the community a standard way to place complex spatial shapes inside scientific data containers.

1.3Relationship to GeoParquet

Marquet follows the semantics of GeoParquet 2.0 as published in the release candidate v2.0.0-rc.1 (July 2026), together with the optional bounding-box covering columns that were restored to the specification after that release candidate. GeoParquet 2.0.0 had not been finalized when this draft was written; a later minor revision of Marquet will follow any differences in the final release. Marquet adopts GeoParquet’s vocabulary and defaults wherever they apply: geometries are encoded as Well-Known Binary, the default coordinate reference system is OGC:CRS84, coordinates are always ordered with easting or longitude first, edges are planar unless stated otherwise, and the geometry type names are GeoParquet’s own. Mapping to and from GeoParquet lists the correspondence between GeoParquet and Marquet in both directions, including the items that have no counterpart, and also covers GeoParquet 1.0 and 1.1 files.

The GeoParquet metadata is recorded in attributes on the table group and on each geometry column (§5 to §9). Point geometries may be stored as native coordinates as well as WKB (§7.4). Because rows can be appended to an H5Col table, Marquet also specifies how its summary metadata and search indexes are kept correct as the table changes (§11).

Several extensions have been proposed for GeoParquet since 2.0.0-rc.1: levels of detail, a quantized geometry encoding, a spatial ordering key, page-level spatial statistics, and a raster column format. All of them were at an early or “alpha” stage when this draft was written, and Marquet does not adopt them.

1.4Scope and non-goals

HEP002 specifies:

HEP002 does not specify:

2Conformance

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.

A Marquet table is an H5Col table group that conforms to HEP001 and satisfies every requirement of this specification that applies to it. A producer is Marquet-conformant when every Marquet table it writes is conformant. A consumer is Marquet-conformant when it can read the geometries of every conformant Marquet table, in all three storage forms, without loss, and interpret them according to the metadata defined here. Because every Marquet table is also an H5Col table, a consumer that implements only HEP001 can still read all of its columns, geometry columns included, as ordinary H5Col data.

This specification depends on two provisions of HEP001 that were added to it alongside this proposal: search indexes over list columns (HEP001, search indexes over list columns) and the scoping of names defined by HEPs layered on H5Col to the table groups that carry their marker attribute (rule 7 of HEP001 reserved names). See Relationship to HEP001.

As in HEP001, the names of integer and floating-point datatypes in this document, such as uint64 or float64, constrain width and signedness but not byte order; HDF5 converts byte order on read. The bytes of a WKB value are an exception: they are stored in HDF5 as uninterpreted uint8 or opaque data, and each WKB value declares its own byte order internally (§7.1).

3Terminology

The terms defined in HEP001 — table group, column dataset, list column, column, row, NROWS, and search index dataset among them — keep their meanings here. The following terms are added.

Marquet table
An H5Col table group that carries the MARQUET_VERSION attribute and conforms to this specification.
Geometry
A value of one of the seven geometry types of the OGC Simple Features model: Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, or GeometryCollection, in one of the coordinate dimensions XY, XYZ, XYM, or XYZM.
Geometry column
A column of a Marquet table that holds one geometry, or a null value, per row. Geometry columns are listed by the table group’s GEOMETRY_COLUMNS attribute and carry the attributes of §6.
Primary geometry column
The geometry column that consumers use by default when a table has several, identified by the first element of GEOMETRY_COLUMNS.
Storage form
The HDF5 representation of a geometry column: a variable-length WKB column (an H5Col list column), a fixed-length WKB column (a column dataset of an opaque datatype), or a point column (a column dataset of a compound datatype). See §7.
WKB
The ISO variant of the OGC Well-Known Binary representation of a geometry, as used by GeoParquet and by Parquet’s native geospatial types. See §7.1.
Null geometry and empty geometry
A null geometry is a missing value: the row has no geometry at all. An empty geometry is a geometry that has no coordinates, such as POINT EMPTY or POLYGON EMPTY. The two are distinct, as they are in WKB and GeoParquet.
Coordinate reference system (CRS)
The definition that relates coordinate values to locations on, above, or below the Earth, given in Marquet as a PROJJSON document.
Row block
A run of consecutive rows whose geometries are summarized by one entry of a block-based search index. See §10.2.
Covering column
A column dataset that holds, for each row, a bounding box of that row’s geometry, so that consumers can test rows without decoding geometries. See §10.5.

4Data model overview

A Marquet table is an H5Col table group with one additional attribute that identifies it, MARQUET_VERSION, and another that lists its geometry columns, GEOMETRY_COLUMNS. Each geometry column is an ordinary H5Col column — either a list column group or a rank-1 column dataset — that carries a small set of attributes describing its geometries. Every other column of the table, such as a feature identifier, a name, or a measurement, is an ordinary H5Col column with all of H5Col’s features. Spatial search indexes live in the table’s SEARCH_INDEXES group alongside any H5Col search indexes, and they are linked from the geometry columns they accelerate exactly as H5Col links indexes to columns. Marquet introduces no new kinds of HDF5 objects below the table group, so H5Col’s rule that a table group is a self-contained subtree holds unchanged.

The diagram below shows a table of lakes with three geometry-related columns: a polygon column stored as variable-length WKB, which is the primary geometry column; a column of lake centroids stored as native points; and a covering column holding each polygon’s bounding box. Two spatial search indexes accelerate the polygon column.

The rest of this document specifies the table group (§5), geometry columns and their attributes (§6), the storage forms (§7), coordinate reference systems (§8), edges and orientation (§9), spatial search indexes and covering columns (§10), and the rules for modifying a Marquet table (§11).

5The Marquet table group

5.1Identification — the MARQUET_VERSION attribute

Every Marquet table group MUST be a conformant H5Col table group and MUST carry an attribute named MARQUET_VERSION with:

A consumer MUST identify a group as a Marquet table group by the presence of both an H5Col CLASS attribute equal to COLUMN_TABLE and a MARQUET_VERSION attribute. A producer MUST NOT write MARQUET_VERSION on a table group that does not satisfy the rest of this specification. The strict-write, lenient-read rule that HEP001 applies to its own string tokens applies to MARQUET_VERSION and to every other Marquet token attribute: producers write exactly the datatype given here, and consumers SHOULD accept any HDF5 string datatype whose logical value matches.

MARQUET_VERSION is Marquet’s marker attribute in the sense of HEP001’s naming rule 7 (HEP001 reserved names). The other names and tokens that this specification defines have their Marquet meaning only in a table group that carries it (Reserved names). A group that carries attributes such as GEOMETRY_COLUMNS but no MARQUET_VERSION is not a Marquet table, and a consumer MUST NOT interpret those attributes as Marquet metadata.

MARQUET_VERSION uses the same two-component MAJOR.MINOR scheme as the H5Col VERSION attribute. MINOR increases with backward-compatible additions, such as a new geometry encoding that older consumers can recognize as unsupported without misreading the table, and MAJOR increases with changes that an existing consumer could misinterpret. Consumers MUST compare versions numerically. A consumer that encounters a MAJOR greater than the highest it implements MUST NOT interpret the table’s geometry columns as geometries, although it MAY still read the table as an ordinary H5Col table.

5.2The GEOMETRY_COLUMNS attribute

Every Marquet table group MUST carry a one-dimensional attribute named GEOMETRY_COLUMNS whose elements are HDF5 object references. Following the rule HEP001 sets for its own reference attributes (HEP001 object references), the attribute’s datatype MUST be the standard reference datatype H5T_STD_REF, and so MUST the datatype of every other reference attribute that this specification defines. The attribute MUST have at least one element. Every element MUST resolve to a column of the table — a column dataset or a list column group that is a direct child of the table group — and MUST NOT be a null reference, and the same column MUST NOT be referenced more than once. Every column referenced by GEOMETRY_COLUMNS is a geometry column and MUST satisfy §6; conversely, every column that carries a GEOMETRY_ENCODING attribute MUST be referenced by GEOMETRY_COLUMNS.

The first element of GEOMETRY_COLUMNS designates the table’s primary geometry column, the counterpart of GeoParquet’s primary_column. When a table has several geometry columns — for instance a building’s footprint and its entrance point — consumers SHOULD use the primary geometry column by default for display, spatial filtering, and spatial joins. The order of the remaining elements carries no meaning.

The attribute uses object references rather than column names for the same reason HEP001’s INDEX_COLUMNS does: a reference keeps pointing at the right column when the column is renamed, and it can never be confused by column names that happen to contain unusual characters.

5.3Feature identifiers

GeoParquet recommends that producers record which column holds the feature identifiers in metadata of their own choosing. In a Marquet table, this role is filled by the row labels of the table, declared by the table group’s INDEX_COLUMNS attribute (HEP001 row index columns). Producers SHOULD declare a table’s feature identifier column as its outermost row index column, INDEX_COLUMNS[0]. A geometry column MUST NOT be referenced by INDEX_COLUMNS, because a geometry describes where a feature is, not which feature it is, and so cannot serve as a row label.

Marquet defines no further attributes on the table group. All of H5Col’s optional table group attributes, such as TITLE, description, and column-order, keep their meanings, and geometry columns and covering columns appear in column-order like any other column.

6Geometry columns

6.1Common requirements

A geometry column is a column of a Marquet table that holds one geometry, or a null value, in each row. Every geometry column:

  1. MUST be a column of the table in the sense of HEP001 — a rank-1 column dataset or a top-level list column group — and MUST be referenced by the table group’s GEOMETRY_COLUMNS attribute;

  2. MUST carry the GEOMETRY_ENCODING and GEOMETRY_TYPES attributes;

  3. MUST use one of the three storage forms of §7, as determined by its GEOMETRY_ENCODING value and its HDF5 object type;

  4. MAY carry the attributes CRS, EPOCH, EDGES, ORIENTATION, BBOX, and COVERING, besides the attributes that HEP001 defines for the column’s HDF5 object — CLASS and KIND on a list column group, SEARCH_INDEX_LIST, and the descriptive attribute description;

  5. MUST NOT carry the H5Col attributes CATEGORIES, valid_min, or valid_max, none of which has a meaning for geometries, and SHOULD NOT carry units or units_vocabulary, because the units of coordinates are defined by the coordinate reference system. When a geometry column does carry units, consumers MUST take coordinate units from the coordinate reference system instead.

All geometries in one geometry column share that column’s coordinate reference system, coordinate epoch, edge interpretation, and orientation guarantee. Different geometry columns of the same table MAY differ in any of them.

The enumerations and compound datatypes that this specification defines are stored with the attributes and datasets that use them. Producers MUST NOT store them as named (committed) datatypes below the table group, which HEP001’s rule that a table group contains only its columns and reserved subgroups would not allow.

The table below summarizes the attributes that Marquet defines for a geometry column. Each is specified in the section given in the last column.

Table 1:Geometry column attributes

AttributeHDF5 datatype and shapeRequiredGeoParquet counterpartSection
GEOMETRY_ENCODINGscalar, fixed-length ASCII tokenyesencoding§6.2
GEOMETRY_TYPES1-D, Marquet geometry-type enumerationyesgeometry_types§6.3
CRSscalar, fixed-length UTF-8 PROJJSON text, or null dataspacenocrs§8.1
EPOCHscalar, floating pointnoepoch§8.3
EDGESscalar, Marquet edges enumerationnoedges; GEOGRAPHY algorithm§9.1
ORIENTATIONscalar, Marquet orientation enumerationnoorientation§9.2
BBOXscalar, compound of float64 boundsnobbox§6.4
COVERINGscalar, H5T_STD_REF object referencenocovering§10.5

6.2The GEOMETRY_ENCODING attribute

Every geometry column MUST carry a scalar, null-terminated, fixed-length ASCII attribute named GEOMETRY_ENCODING whose value names how the column’s geometries are encoded. This revision defines two values:

WKB
Each geometry is a WKB value (§7.1). The column is either a top-level H5Col list column group, in which case it is a variable-length WKB column (§7.2), or a column dataset of an H5T_OPAQUE datatype, in which case it is a fixed-length WKB column (§7.3).
POINT
Each geometry is a point stored as native coordinates. The column is a column dataset of a compound datatype (§7.4).

The encoding names what the stored values mean, and the HDF5 object that holds them determines the storage form, so a consumer needs no second attribute to tell the forms apart. Any combination of GEOMETRY_ENCODING value and HDF5 object other than the three listed above is non-conformant.

A consumer that encounters a GEOMETRY_ENCODING value it does not implement MUST NOT silently drop the column. It SHOULD present the column as a geometry column it cannot decode; the column remains readable as ordinary H5Col data. Future revisions MAY register native encodings for other geometry types; the tokens LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, and MULTIPOLYGON are reserved for that purpose.

6.3The GEOMETRY_TYPES attribute

Every geometry column MUST carry a one-dimensional attribute named GEOMETRY_TYPES that lists the kinds of geometry the column contains. Its datatype is the Marquet geometry-type enumeration: an HDF5 enumerated datatype with base type H5T_STD_U16LE and the 28 members shown in Table 2. Each member name is the geometry type string that GeoParquet uses in its geometry_types metadata, and each member value is the ISO WKB type code that Parquet uses in its geospatial statistics, so a single attribute carries both vocabularies.

Table 2:Members of the Marquet geometry-type enumeration (member name and value)

Geometry typeXYXYZXYMXYZM
Point"Point" = 1"Point Z" = 1001"Point M" = 2001"Point ZM" = 3001
LineString"LineString" = 2"LineString Z" = 1002"LineString M" = 2002"LineString ZM" = 3002
Polygon"Polygon" = 3"Polygon Z" = 1003"Polygon M" = 2003"Polygon ZM" = 3003
MultiPoint"MultiPoint" = 4"MultiPoint Z" = 1004"MultiPoint M" = 2004"MultiPoint ZM" = 3004
MultiLineString"MultiLineString" = 5"MultiLineString Z" = 1005"MultiLineString M" = 2005"MultiLineString ZM" = 3005
MultiPolygon"MultiPolygon" = 6"MultiPolygon Z" = 1006"MultiPolygon M" = 2006"MultiPolygon ZM" = 3006
GeometryCollection"GeometryCollection" = 7"GeometryCollection Z" = 1007"GeometryCollection M" = 2007"GeometryCollection ZM" = 3007

Producers MUST write GEOMETRY_TYPES with exactly this enumeration, all 28 members included, so that the datatype is identical in every Marquet file and in every geometry column. Consumers MUST interpret the attribute’s elements by member name, not by integer value, and SHOULD accept any enumeration whose member names are drawn from the table above, whatever its base type. In the DDL notation emitted by h5dump, an attribute declaring a column of polygons and multipolygons looks as follows, with most of the enumeration’s members omitted here for brevity:

ATTRIBUTE "GEOMETRY_TYPES" {
   DATATYPE  H5T_ENUM {
      H5T_STD_U16LE;
      "GeometryCollection" 7;
      …
      "MultiPolygon"     6;
      …
      "Polygon"          3;
      …
   }
   DATASPACE  SIMPLE { ( 2 ) / ( 2 ) }
   DATA {
   (0): Polygon, MultiPolygon
   }
}

The attribute’s elements MUST be distinct. An attribute with zero elements (a one-dimensional dataspace of extent 0) declares that the column’s geometry types are not known. Otherwise, the attribute MUST list the type and coordinate dimension of every non-null geometry, empty geometries included, in rows [0, NROWS) of the column. It is not enough to list MultiPolygon for a column that also contains polygons; both Polygon and MultiPolygon must be listed, and three-dimensional points must be listed as Point Z rather than Point. The types of the geometries inside a GeometryCollection are not listed; the collection is listed as GeometryCollection.

The list MAY also name types that do not occur in the column. Producers SHOULD keep it exact where they can, and a producer that only appends rows normally does. A truncation, an in-place update, or an append interrupted by a crash, however, can leave the list naming types that no longer occur, and the list then remains correct (§11). GeoParquet’s geometry_types must be exact; Mapping to and from GeoParquet describes how an exporter produces it from this attribute.

For a point column (§7.4) the geometry type is fixed by the column’s compound datatype. Its GEOMETRY_TYPES attribute MUST either be empty or contain exactly the one member that matches the column’s coordinate fields: Point for {x, y}, Point Z for {x, y, z}, Point M for {x, y, m}, and Point ZM for {x, y, z, m}.

6.4The BBOX attribute

A geometry column MAY carry a scalar attribute named BBOX that gives a bounding box of all its geometries; it corresponds to GeoParquet’s bbox field (Mapping to and from GeoParquet). Its datatype is a compound datatype whose fields are those of the GeoArrow box type:

Every field MUST be a 64-bit IEEE floating-point value, the fields MUST appear in the order listed, and no other fields are allowed. Consumers MUST identify the fields by name. The x and y bounds are always present. Producers MAY include the z bounds, the m bounds, or both, but only when the column’s geometries have those dimensions.

The bounds are expressed in the column’s coordinate reference system and in Marquet’s axis order (§8.2). They MUST contain every non-null, non-empty geometry in rows [0, NROWS), in the following sense, which the rest of this specification also uses. A box contains a geometry when it contains every point of the geometry: every vertex, ignoring coordinate values that are NaN; every point along every edge, under the column’s edge interpretation (§9.1); and, for a polygon, every point of its interior. With planar edges, a box that contains the vertices contains the whole geometry. With any other edge interpretation it may not. A geodesic between two vertices bulges toward the nearer pole, beyond the latitudes of its vertices, and a polygon whose interior includes a pole contains that pole and every longitude around it, so its box must reach latitude 90 (or −90) and span all longitudes. A box MAY be larger than necessary; one that encloses the geometries loosely is still correct (§11 explains why).

For a column whose coordinate reference system is geographic, a box MAY be written with xmin greater than xmax, as in GeoJSON (RFC 7946, section 5), to describe geometries that cross the antimeridian compactly. Such a box covers the longitudes from xmin eastward through 180 and from −180 eastward to xmax: a longitude x lies within it when x ≥ xmin or x ≤ xmax, whereas it lies within an ordinary box when xmin ≤ x ≤ xmax. For any other coordinate reference system, xmin MUST NOT exceed xmax. A producer SHOULD omit BBOX when the column holds no non-null, non-empty geometry.

ATTRIBUTE "BBOX" {
   DATATYPE  H5T_COMPOUND {
      H5T_IEEE_F64LE "xmin";
      H5T_IEEE_F64LE "ymin";
      H5T_IEEE_F64LE "xmax";
      H5T_IEEE_F64LE "ymax";
   }
   DATASPACE  SCALAR
   DATA {
   (0): {
         -66.42,
         43.38,
         -59.68,
         47.04
      }
   }
}

The COVERING attribute, which links a geometry column to a column of per-row bounding boxes, is specified with covering columns in §10.5.

7Geometry encodings and storage forms

A geometry column stores its geometries in one of three storage forms. Two of them hold WKB, the encoding GeoParquet and Parquet use, and differ only in how the bytes of each value are laid out in HDF5. The third holds points as native floating-point coordinates. The figure below shows all three; the subsections that follow specify each one.

7.1Well-known binary

A WKB value in Marquet is the ISO variant of the Well-Known Binary representation defined by the OGC Simple Features Access specification, Part 1 (version 1.2.1), and by ISO/IEC 13249-3:2016, which is also the variant used by GeoParquet and by Parquet’s native geospatial types. Each value begins with a byte-order flag (0x00 for big-endian, 0x01 for little-endian), followed by a 32-bit geometry type code in that byte order and then the counts and coordinates of the geometry. The type codes are exactly the member values of the geometry-type enumeration (Table 2): 1 to 7 for two-dimensional geometries, with 1000, 2000, and 3000 added for the Z, M, and ZM variants.

Every WKB value in a Marquet table MUST encode exactly one geometry of one of the seven geometry types, and so MUST every geometry nested inside a GeometryCollection. A WKB value MUST NOT use the “extended” WKB conventions of some databases, such as an embedded spatial reference identifier or Z and M flags in the high bits of the type code, and MUST NOT be followed by trailing bytes. Producers SHOULD write little-endian WKB; consumers MUST accept both byte orders, including different byte orders in different values of one column and in the nested geometries of one collection. Coordinates follow Marquet’s axis order (§8.2). An empty point is written, as WKB has no other way to express it, as a point whose coordinates are all NaN.

7.2Variable-length WKB columns

A variable-length WKB column can hold geometries of any type, of any size, and of mixed types and dimensions, which makes it the general-purpose storage form. It is an H5Col list column (HEP001 list columns) whose elements are the bytes of each row’s WKB value:

To read the geometry of row i, a consumer reads MASK[i] if the mask exists and returns a null geometry when it is FALSE; otherwise it reads OFFSETS[i] and OFFSETS[i+1] and decodes the WKB value held by that slice of VALUES. As HEP001 notes for list columns in general, this takes two dependent reads: one of the small offsets dataset and one of the value bytes.

Each member dataset chooses its own chunking and filters. The value bytes compress well with general-purpose compressors such as Zstandard, and a chunk size of about 1 MiB is a reasonable starting point. The shuffle filter has no effect on one-byte elements and need not be applied to VALUES.

7.3Fixed-length WKB columns

When every WKB value in a column has the same length, the offsets of a list column carry no information, and the values can instead be stored as the elements of an ordinary rank-1 column dataset. This is always the case for points of a single coordinate dimension, whose WKB values are 21 bytes long in two dimensions, 29 bytes with Z or with M, and 37 bytes with both. It can also hold for less common columns whose geometries all share one shape, such as two-vertex line segments (41 bytes in two dimensions) or rectangles stored as five-vertex polygons (93 bytes in two dimensions).

A fixed-length WKB column is a rank-1 H5Col column dataset that satisfies the following:

Because the column is a rank-1 column dataset, a consumer reads any row with a single read, and the column can carry search indexes, including H5Col’s own CHUNK_BLOOM index for exact-match lookups of WKB values. Converting point WKB from GeoParquet into this form, or back, is a byte-for-byte copy of each value.

The shuffle filter, applied to elements of the opaque datatype’s size, gathers each byte position of all elements together, which separates the constant WKB header bytes from the byte planes of each coordinate and usually improves compression considerably for two-dimensional points. For three-dimensional points the benefit varies with the data, and producers should measure it.

7.4Point columns

A point column stores each point as native floating-point coordinates rather than as WKB. The coordinates are then ordinary numbers that any HDF5 reader can use without a WKB decoder, that compress as well as any floating-point data, and that consumers can process in bulk — for example, to compute bounding boxes or to transform coordinates — without decoding each geometry. A point column is a rank-1 H5Col column dataset that satisfies the following:

In h5dump DDL, a two-dimensional point column with its recommended fill value looks as follows:

DATASET "location" {
   DATATYPE  H5T_COMPOUND {
      H5T_IEEE_F64LE "x";
      H5T_IEEE_F64LE "y";
   }
   DATASPACE  SIMPLE { ( 3 ) / ( 3 ) }
   FILLVALUE {
      FILL_TIME H5D_FILL_TIME_IFSET
      VALUE  {
         9.96921e+36,
         9.96921e+36
      }
   }
   DATA {
   (0): {
         -88.2434,
         40.1164
      },
   (1): {
         9.96921e+36,
         9.96921e+36
      },
   (2): {
         nan,
         nan
      }
   }
}

Row 0 is a point, row 1 is null, and row 2 is an empty point. The shuffle filter, applied to the compound’s element size, separates the byte planes of the coordinate fields in the same way it does for fixed-length WKB, and is RECOMMENDED. A producer MAY reduce the precision of the coordinates before compression, for instance by rounding away low-order mantissa bits, either before writing or through a filter that accepts compound datatypes with floating-point fields. A producer that does so SHOULD record the precision it retained in the column’s description attribute.

7.5Null and empty geometries

The table below summarizes how each storage form expresses the three kinds of row value.

Table 3:Point, null, and empty values in the three storage forms

Row valueVariable-length WKBFixed-length WKBPoint column
A geometryits WKB bytes in VALUESits WKB bytes as one opaque element{x, y[, z][, m]}
Null geometryMASK[i] is FALSE; the row occupies no bytesthe column’s fill valuethe column’s fill value
Empty geometrythe WKB value of the empty geometrythe WKB value of the empty geometryNaN in every field

Null geometries are excluded from bounding boxes and from spatial search indexes, so a consumer never finds them through a spatial index. Empty geometries are likewise excluded from bounding boxes and indexes, because they have no coordinates, but they are geometries: their types are listed in GEOMETRY_TYPES, and they are counted separately from nulls by the block bounding-box index (§10.3).

7.6Choosing a storage form

Variable-length WKB is the general form and the only one that accepts values of differing lengths, which in practice makes it the form for lines, polygons, geometry collections, and columns that mix geometry types or dimensions. A producer that converts GeoParquet data can always use it, copying each WKB value unchanged.

For points, the two rank-1 forms are smaller and faster than variable-length WKB, because they need neither an offsets dataset nor a second read per row. Between the two, fixed-length WKB keeps the bytes identical to GeoParquet’s, which makes exchange a straight copy, while a point column exposes the coordinates as named numbers, which suits analysis, bulk coordinate processing, readers without a WKB decoder, and precision reduction before compression. Producers SHOULD use one of the rank-1 forms for point data, and SHOULD prefer a point column unless byte-for-byte WKB compatibility matters more to their users.

8Coordinate reference systems

8.1The CRS attribute

A geometry column MAY carry a scalar attribute named CRS that defines the coordinate reference system of its geometries. The attribute takes one of three states, which correspond one to one with the three states of GeoParquet’s crs field:

Absent
The coordinate reference system is OGC:CRS84: longitude and latitude, in that order, in degrees, on the WGS 84 datum. This is the default in GeoParquet and in Parquet.
Present with a value
The value is a PROJJSON document — a single JSON object, encoded in UTF-8, that conforms to the PROJJSON specification — describing the coordinate reference system. The attribute’s datatype MUST be a fixed-length UTF-8 string, null-terminated or null-padded, sized to hold the document. Consumers SHOULD also accept a variable-length string.
Present with a null dataspace
An attribute named CRS whose dataspace is the HDF5 null dataspace (H5S_NULL) declares that the coordinate reference system is undefined or unknown. This is GeoParquet’s "crs": null. The attribute’s datatype SHOULD be a string datatype; consumers MUST NOT rely on it.

PROJJSON is a JSON encoding of the WKT2:2019 (ISO 19162:2019) representation of coordinate reference systems, and it can describe any coordinate reference system that WKT2 can, including projected, compound, and dynamic ones, without reference to an external registry. GeoParquet requires PROJJSON in its own metadata for exactly that reason, and Marquet, which has only one place to record the coordinate reference system, requires it too. A producer MUST NOT write an authority code such as EPSG:4326, a WKT string, or any other form in the CRS attribute. When the coordinate reference system has an identifier assigned by an authority, producers SHOULD include it in the PROJJSON id member, so that consumers without a full PROJJSON parser can still recognize common systems.

All geometries of a column share its coordinate reference system; per-row coordinate reference systems are outside the scope of this specification.

8.2Coordinate axis order

Coordinates in Marquet are always ordered with easting or longitude as x and northing or latitude as y, regardless of the axis order that the coordinate reference system itself declares. This is the long-standing convention of WKB and the rule of GeoParquet and Parquet, and it applies to WKB values, point columns, the BBOX attribute, covering columns, and search indexes alike. One consequence is that EPSG:4326, whose declared axis order is latitude first, and OGC:CRS84 describe the same coordinates in Marquet.

8.3The EPOCH attribute

In a dynamic coordinate reference system, such as one tied to a continuously updated realization of the International Terrestrial Reference Frame, the coordinates of a point on the Earth’s surface change slowly with time, so coordinates are unambiguous only when qualified by the epoch at which they are valid. A geometry column whose coordinate reference system is dynamic MAY carry a scalar floating-point attribute named EPOCH giving that epoch as a decimal year, for example 2021.47. Producers SHOULD write it as a 64-bit floating-point value. As in GeoParquet, the epoch applies to the whole column; per-geometry epochs are not supported.

8.4OGC:CRS84

The PROJJSON document for OGC:CRS84, the default coordinate reference system, is the one GeoParquet gives:

{
    "$schema": "https://proj.org/schemas/v0.5/projjson.schema.json",
    "type": "GeographicCRS",
    "name": "WGS 84 longitude-latitude",
    "datum": {
        "type": "GeodeticReferenceFrame",
        "name": "World Geodetic System 1984",
        "ellipsoid": {
            "name": "WGS 84",
            "semi_major_axis": 6378137,
            "inverse_flattening": 298.257223563
        }
    },
    "coordinate_system": {
        "subtype": "ellipsoidal",
        "axis": [
        {
            "name": "Geodetic longitude",
            "abbreviation": "Lon",
            "direction": "east",
            "unit": "degree"
        },
        {
            "name": "Geodetic latitude",
            "abbreviation": "Lat",
            "direction": "north",
            "unit": "degree"
        }
        ]
    },
    "id": {
        "authority": "OGC",
        "code": "CRS84"
    }
}

A consumer that works only with longitude and latitude and cannot fully parse PROJJSON MAY treat a column as OGC:CRS84 when its CRS attribute is absent, or when the PROJJSON id member names either OGC / CRS84 or EPSG / 4326; the second is equivalent because of the axis-order rule above.

9Edges and orientation

9.1The EDGES attribute

The coordinates of a line or polygon define its vertices; the edges between consecutive vertices also need an interpretation. They can be straight lines in the coordinate space, which is how most projected data is meant, or geodesics on the sphere or the ellipsoid, which is how global longitude-latitude data is often meant. A geometry column MAY carry a scalar attribute named EDGES that declares the interpretation. Its datatype is the Marquet edges enumeration, an HDF5 enumerated datatype with base type H5T_STD_U8LE and the members below; producers MUST write it with exactly these six members, and consumers MUST interpret it by member name.

Table 4:Members of the Marquet edges enumeration

MemberValueEdges between consecutive vertices follow …
PLANAR0straight lines in the coordinate space (Cartesian interpolation)
SPHERICAL1the shortest path on a sphere (a great-circle arc), as in BigQuery and Snowflake geographies and Google’s S2 library
VINCENTY2the geodesic on the coordinate reference system’s ellipsoid, computed by Vincenty’s formulae
THOMAS3the geodesic on the ellipsoid, computed by Thomas’s 1970 formulae for spheroidal geodesics
ANDOYER4the geodesic on the ellipsoid, computed by the Andoyer–Lambert method as given by Thomas (1965)
KARNEY5the geodesic on the ellipsoid, computed by Karney’s algorithms (2013) as implemented in GeographicLib

When the attribute is absent, edges are PLANAR, which matches the default of GeoParquet and the semantics of Parquet’s GEOMETRY type. Every other member corresponds to Parquet’s GEOGRAPHY type with the edge-interpolation algorithm of the same name. A geometry column whose EDGES is not PLANAR MUST have a geographic coordinate reference system, with longitudes in [−180, 180] and latitudes in [−90, 90]. The edge interpretation has no effect on point geometries, and producers SHOULD NOT write EDGES on columns that hold only points.

A consumer that does not recognize the member name of an EDGES attribute MUST NOT assume planar edges; it SHOULD report the column’s edges as unsupported.

9.2The ORIENTATION attribute

A polygon’s rings can be listed clockwise or counterclockwise, and for planar edges the order rarely matters. On the sphere, however, a ring divides the surface into two regions, and the direction in which the ring is traversed is what tells which of them is the polygon’s interior. A geometry column MAY carry a scalar attribute named ORIENTATION that guarantees a winding order. Its datatype is the Marquet orientation enumeration, an HDF5 enumerated datatype with base type H5T_STD_U8LE and a single member, COUNTERCLOCKWISE with value 1; producers MUST write it with exactly this member.

When the attribute is present with the value COUNTERCLOCKWISE, the exterior ring of every polygon in the column — including every polygon of every multipolygon and geometry collection — MUST be ordered counterclockwise, with the polygon’s interior on the left when the ring is traversed, and every interior ring MUST be ordered clockwise. When the attribute is absent, no assertion is made about winding order, and consumers that need a particular order must establish it themselves. As in GeoParquet, it is RECOMMENDED that producers write ORIENTATION whenever EDGES is not PLANAR, because without it the meaning of a polygon on the sphere is ambiguous. A consumer that does not recognize the member name MUST treat the attribute as if it were absent.

10Spatial search indexes and covering columns

Most queries against geospatial data begin with a spatial filter: the features inside a map view, along a route, or near a point. Without help, a consumer must decode every geometry in the column to answer such a query. Marquet offers three optional structures that let a consumer skip most of that work. A block bounding-box index summarizes each run of consecutive rows with one bounding box, so whole runs that lie outside the query area can be skipped. A packed Hilbert R-tree finds the individual rows whose bounding boxes meet the query area. And a covering column stores each row’s bounding box next to its geometry, so a row can be tested without decoding its geometry. None of them is required: a consumer MAY ignore all of them and still return correct answers, only more slowly, exactly as HEP001 provides for its own search indexes.

10.1Marquet search indexes within H5Col

This specification registers two KIND values with H5Col’s search-index framework (HEP001 search indexes): BLOCK_BBOX (§10.3) and PACKED_RTREE (§10.4). Search-index datasets of both kinds are H5Col search-index datasets in every respect. They live in the table group’s SEARCH_INDEXES group, they carry the KIND, SOURCE_GENERATION, and SOURCE_NROWS attributes, a consumer MUST evaluate H5Col’s validity check (HEP001 index validity tokens) before using one, and each is linked from the one geometry column it accelerates through that column’s SEARCH_INDEX_LIST attribute.

HEP001 permits a kind of search index to be declared applicable to list columns (HEP001 search indexes over list columns), and this specification declares both of its kinds so. They can therefore accelerate geometry columns in all three storage forms: variable-length WKB columns, which are list column groups, as well as fixed-length WKB columns and point columns, which are column datasets. A search index of either kind MUST be referenced only from the SEARCH_INDEX_LIST of a geometry column, and it describes that column’s geometries.

HEP001’s own four kinds of search index describe the order or the exact values of a column’s elements, which carry no spatial meaning for geometries. HEP001 already forbids all four on list columns, and forbids CHUNK_MINMAX, SORTED_ROWS, and CHUNK_BLOOM on compound columns, which rules them out for variable-length WKB columns and, apart from BITMAP, for point columns. Producers SHOULD NOT build BITMAP indexes over point columns, nor CHUNK_MINMAX, SORTED_ROWS, or BITMAP indexes over fixed-length WKB columns. A CHUNK_BLOOM index over a fixed-length WKB column MAY be used for exact-match lookups of WKB values.

Both of Marquet’s index kinds are built from boxes that contain geometries, in the sense defined for the BBOX attribute (§6.4): a box contains a geometry when it contains every vertex, every point along every edge under the column’s edge interpretation, and every point of a polygon’s interior. Z and M coordinates do not take part in either index, except through the optional Z and M fields of the block bounding-box index.

10.2Row blocks

The block bounding-box index summarizes runs of consecutive rows called row blocks. For a block length B, a positive integer chosen by the producer and recorded with the index, row block j consists of rows [j·B, min((j+1)·B, NROWS)). The table therefore has ceil(NROWS / B) row blocks, of which only the last may be shorter than B, and none when NROWS = 0.

Row blocks are defined in terms of rows rather than HDF5 chunks for two reasons. HEP001 lets every column of a table choose its own chunk shape, so the columns of a table need not share any chunk boundaries. And a geometry column’s rows and its storage chunks do not correspond one to one in every storage form: in a variable-length WKB column, a row’s bytes can fall anywhere in the VALUES dataset. Because B is recorded with the index rather than taken from a dataset’s chunk shape, the index also stays correct when the table’s datasets are rechunked.

Producers SHOULD choose B so that the number of rows in one chunk of every column dataset of the table, and of the OFFSETS dataset of every variable-length WKB column, is a multiple of B. Every chunk of those datasets then holds a whole number of row blocks, so a chunk whose row blocks a query skips is never read. Chunks whose row counts are powers of two make this easy: B equal to the smallest of those row counts satisfies the rule, whatever the other counts are. For other row counts, their greatest common divisor satisfies it, but a small divisor makes the index large. A smaller B never lets a query skip fewer rows; it only makes the index larger.

The VALUES dataset of a variable-length WKB column cannot be aligned with row blocks, because its chunks hold a fixed number of bytes while its rows vary in length. For each run of consecutive row blocks that a query does not skip, a consumer reads the VALUES chunks that the run’s bytes span, which costs at most one partly used chunk at each end of the run. Ordering the rows spatially (§10.6) keeps such runs few, because the row blocks that a compact query area touches tend to lie next to each other.

10.3Block bounding-box index (KIND = BLOCK_BBOX)

A block bounding-box index records, for each row block, a bounding box of all the geometries in that block, together with counts of the block’s rows, null geometries, and empty geometries. It is Marquet’s counterpart of the per-row-group geospatial statistics that Parquet stores for GEOMETRY and GEOGRAPHY columns. The index has one entry per row block, and it can be extended in place when rows are appended. It is RECOMMENDED for every geometry column whose table has more than a few row blocks.

The search-index dataset has rank 1, and its first-dimension extent is at least the number of row blocks, ceil(NROWS / B). As HEP001 provides for every search index, entries beyond the first ceil(NROWS / B), left over from preallocation or from a larger earlier state of the table, MAY be present and MUST be ignored. Its datatype is a compound datatype with the following fields, in this order:

The box fields follow the field names and order of the BBOX attribute, and consumers identify all fields by name. For each block, the box MUST contain every non-null, non-empty geometry in the block, and the Z and M bounds, when present, MUST contain all of those geometries’ Z and M values, ignoring NaN; when all of a block’s Z values, or all of its M values, are NaN, the corresponding minimum holds +∞ and the maximum −∞. The box MAY be larger than necessary. When every row of a block is null or empty (null_count + empty_count = n), the box fields MUST hold +∞ in every minimum and −∞ in every maximum, the convention GeoArrow uses for an empty box. Such a box intersects nothing, so a consumer that tests it against a query area skips the block without needing a special case.

For a column whose coordinate reference system is geographic, a block whose geometries straddle the antimeridian MAY be summarized with xmin greater than xmax, with the meaning given for the BBOX attribute: the box covers the longitudes from xmin eastward through 180 and from −180 eastward to xmax. For any other coordinate reference system, xmin MUST NOT exceed xmax except in the placeholder box of a block without geometries.

The search-index dataset carries the attributes that HEP001 requires of every search-index dataset — KIND, with the value BLOCK_BBOX, SOURCE_GENERATION, and SOURCE_NROWS — and one attribute of its own:

block_length
Scalar uint64, at least 1. The block length B of §10.2.

To evaluate a spatial filter with this index, a consumer first checks the index’s validity tokens, then reads the index, which is small, and compares each block’s box with a box containing the query area. When the query’s predicate can hold only for geometries that intersect the query area, as is the case for predicates such as intersects, within, and contains, the blocks whose boxes do not intersect the query box contain no matching rows and are skipped; the rows of the remaining blocks are read and tested against the exact predicate. The counts let a consumer report how many rows a filter skipped and let it answer simple questions — such as how many geometries are null — from the index alone.

When rows are appended, a producer updates the index incrementally: it recomputes the entry of the block that was the last, partial block before the append, and appends entries for the new blocks, following HEP001’s tokens-before-content rule for appends.

10.4Packed Hilbert R-tree index (KIND = PACKED_RTREE)

A block bounding-box index can only skip whole blocks, and it helps most when the table’s rows are ordered so that nearby features sit in nearby rows. A packed R-tree instead finds the individual rows whose bounding boxes meet a query area, whatever the order of the rows, at the cost of storing one entry per row plus a small number of internal nodes. It suits very selective queries over large tables, such as a small map view over a continent-wide dataset. The layout adopted here is the static, packed R-tree used by the FlatGeobuf format, in which the tree is stored level by level in one array and the position of every node’s children can be computed rather than stored as pointers to be chased. The index is OPTIONAL; experience reported by the GeoParquet community suggests that, for tables of up to a few thousand blocks, a block bounding-box index performs about as well.

Items and levels

The index has one item for each row in [0, NROWS) whose geometry is non-null and non-empty; rows with null or empty geometries have no item and can never be returned by the index. The number of items, n₀, is recorded in the num_items attribute. Each item’s box contains the row’s geometry (§6.4) and never wraps around the antimeridian: for a column with a geographic coordinate reference system, the box of a geometry that crosses the antimeridian MUST span the full longitude range, xmin = −180 and xmax = 180.

The items are the tree’s leaves, or level 0. With node size M, recorded in the node_size attribute, each successive level groups the entries of the level below into consecutive runs of M. The level sizes are therefore

n₀ = num_items
n_(k+1) = ceil(n_k / M)        computed at least once, and repeated until n_(k+1) = 1

and the top level, the root, has a single entry. A tree of one item therefore has two levels, a root above a single leaf, as in FlatGeobuf, and a tree of zero items has no levels and no entries.

Dataset layout

The search-index dataset has rank 1 and holds every entry of every level, with the root level first and the leaf level last. If the root is level L, level k begins at position

start_k = n_L + n_(L−1) + … + n_(k+1),        with start_L = 0,

and the levels hold T = n₀ + n₁ + … + n_L entries in total. The dataset’s first-dimension extent is at least T; entries beyond the first T, left over from an earlier, larger state of the table, MAY be present and MUST be ignored. The datatype is a compound datatype with the following fields, in this order:

A leaf entry holds an item’s box, and its index is the item’s row position. An internal entry, at position start_k + i of a level k ≥ 1, has as its children the entries of level k − 1 at positions start_(k−1) + i·M through start_(k−1) + min((i+1)·M, n_(k−1)) − 1. Its box MUST be the smallest box containing the boxes of those children, and its index MUST equal start_(k−1) + i·M, the position of its first child. Storing that position, although it can be computed, lets a reader descend the tree without recomputing the level layout and lets it check the tree’s integrity.

The leaves MAY appear in any order, and the index is correct whatever the order. Its efficiency, however, depends on leaves that are close in space being close in the array, so that the boxes of internal nodes stay small. Producers SHOULD order the leaves along a Hilbert curve, as FlatGeobuf does: by the Hilbert index of each item box’s centre, computed on a grid of 2¹⁶ × 2¹⁶ cells spanning the bounding box of all items, with ties broken by increasing row position.

Attributes

The search-index dataset carries the attributes that HEP001 requires of every search-index dataset — KIND, with the value PACKED_RTREE, SOURCE_GENERATION, and SOURCE_NROWS — and the following attributes of its own:

node_size
Scalar uint16, at least 2. The node size M. Producers SHOULD use 16 unless they have measured a better value for their data.
num_items
Scalar uint64. The number of items n₀, from which the sizes and positions of all levels follow.

Searching

To find the rows whose bounding boxes intersect a query box, a consumer starts from the root entry, at position 0. For every entry whose box intersects the query box, it either reports the entry’s index as a candidate row, when the entry belongs to the leaf level, or visits the entry’s children, when it does not. The candidate rows are then tested against the query’s exact predicate. Because the upper levels are small and come first in the dataset, producers SHOULD chunk the dataset so that the upper levels fall within its first chunk or chunks; a consumer reading over a network can then fetch them with a single request and descend to the leaves with a few more.

A packed R-tree cannot be updated incrementally. When rows are appended, truncated, or modified, a producer rebuilds the index, or leaves it to be disabled by H5Col’s validity check. As HEP001 requires of every search index, a producer rebuilds it in place, resizing and rewriting the existing dataset, rather than deleting and recreating it.

10.5Bounding-box covering columns

A covering column holds, for each row, a bounding box of that row’s geometry. It is Marquet’s counterpart of GeoParquet’s covering.bbox column, and it lets a consumer test rows against a query box, or compute simple spatial statistics, without decoding a single geometry. It is most useful for variable-length WKB columns of lines and polygons, whose geometries are comparatively expensive to read and decode. For columns that hold only points, a covering column would merely repeat the coordinates, and producers SHOULD NOT create one.

A geometry column MAY carry a scalar attribute named COVERING whose value is an HDF5 object reference, of datatype H5T_STD_REF, to its covering column. The covering column:

For each row whose geometry is non-null and non-empty, the covering column holds a box that contains the geometry (§6.4), with Z bounds that contain its Z values when the Z fields are present. For a column with a geographic coordinate reference system, a box MAY wrap around the antimeridian with xmin greater than xmax, as GeoParquet allows. A row whose geometry is empty holds the empty box, +∞ in each minimum and −∞ in each maximum. A row whose geometry is null holds the covering column’s fill value, which, as for every H5Col column dataset, MUST be declared explicitly; producers SHOULD set every field of it to 9.9692099683868690e+36, HEP001’s recommended floating-point fill value. Conversely, a row of the covering column holds the fill value only when the geometry of that row is null.

A covering column is an ordinary column. Marquet’s index kinds do not apply to it, and HEP001’s order-based kinds and Bloom filters do not apply to compound columns.

10.6Spatial ordering of rows

Block bounding boxes help only when the rows of each block lie close together in space; when the rows of a table are in arbitrary order, almost every block spans the whole dataset and almost none can be skipped. GeoParquet’s experience is that spatially ordering the rows before writing them is the single most effective way to make spatial filters fast, and Marquet tables behave the same way. Producers SHOULD therefore order the rows of a table spatially by its primary geometry column, for example along a Hilbert curve through the centres of the geometries’ bounding boxes, unless another order matters more to the table’s users. This specification does not record the order in which rows were sorted; a proposal to standardize a spatial ordering key was still at an early stage in GeoParquet when this draft was written, and it is left to a future revision.

11Writing and appending data

A Marquet table is modified exactly as any H5Col table is, following HEP001’s rules for appending rows, preallocating storage, truncating, and updating values in place (HEP001 writing and appending data). Those rules keep the table’s columns and search indexes consistent, including across a crash, by publishing appended rows only when NROWS is committed and by disabling stale search indexes through the validity tokens. Marquet’s own search indexes are covered by those rules without change. This section adds what is specific to geometry columns.

11.1Keeping summary attributes correct

The BBOX and GEOMETRY_TYPES attributes summarize the whole column. Unlike search indexes, they carry no validity tokens: a consumer cannot tell whether a summary attribute is out of date, so a producer MUST NOT let it fall behind the data. Marquet makes this practical by defining both attributes conservatively. BBOX may enclose more than the geometries, and GEOMETRY_TYPES may, after some kinds of change, list types that no longer occur (§6.3). A summary that has only ever grown remains correct, so a producer keeps it correct by widening it before the data it must cover becomes visible to readers:

A producer that has rewritten or scanned a whole column MAY tighten both attributes to the exact values. Producers that only ever append rows keep the attributes exact as a matter of course, unless an append is interrupted.

These rules mean that a producer that does not implement this specification cannot safely modify a Marquet table. An H5Col tool that knows nothing of Marquet and appends rows would leave BBOX, GEOMETRY_TYPES, and any covering column behind the data, and no consumer could notice. A producer that does not implement this specification MUST NOT append rows to a Marquet table or update its geometry or covering columns in place. It MAY truncate the table and update its other columns, which leaves every Marquet structure correct or, in the case of search indexes, detectably stale.

11.2Constraints of the storage forms

Two of the storage forms restrict the values that can be appended or written in place. Every new value in a fixed-length WKB column has to have exactly the column’s element size, and every new value in a point column has to have the column’s coordinate fields. A producer that needs to store a value that does not fit, such as a three-dimensional point in a two-dimensional point column or a polygon in a fixed-length column of points, first replaces the column with one in a suitable form. The same applies when a column’s coordinates are transformed to another coordinate reference system, because the CRS attribute and the stored coordinates have to change together.

HDF5 cannot replace one column with another atomically, and HEP001 leaves the swapping of columns to a future revision, so a replacement passes through a brief state in which the table does not satisfy requirement 3 of §12. Producers SHOULD keep that state as brief as possible by replacing a geometry column in the following order:

  1. Write the new column in full, with its covering column if it is to have one, and with every geometry column attribute except GEOMETRY_ENCODING. Until the next step, the new column is an ordinary H5Col column.

  2. Write the new column’s GEOMETRY_ENCODING, and then rewrite GEOMETRY_COLUMNS so that it references the new column in place of the old one, at the same position. Between these two attribute writes, the table violates requirement 3.

  3. Build the new column’s search indexes, if any.

  4. Delete the old column, together with its covering column and its search indexes, and update column-order.

A consumer that finds a column carrying GEOMETRY_ENCODING that is not referenced by GEOMETRY_COLUMNS SHOULD treat only the columns that GEOMETRY_COLUMNS references as geometry columns.

12Consistency requirements

A conformant Marquet table satisfies all of the following at all times, in addition to every consistency requirement of HEP001:

  1. The table group is a conformant H5Col table group and carries a MARQUET_VERSION attribute (§5.1).

  2. The table group carries a GEOMETRY_COLUMNS attribute with at least one element, whose elements are non-null H5T_STD_REF object references to distinct columns of the table (§5.2).

  3. Every column referenced by GEOMETRY_COLUMNS carries GEOMETRY_ENCODING and GEOMETRY_TYPES, and every column that carries GEOMETRY_ENCODING is referenced by GEOMETRY_COLUMNS (§6.1).

  4. No geometry column is referenced by the table group’s INDEX_COLUMNS attribute, and no geometry column carries CATEGORIES, valid_min, or valid_max (§5.3, §6.1).

  5. Each geometry column’s GEOMETRY_ENCODING value and HDF5 object form one of the three storage forms: WKB with a top-level list column group, WKB with a column dataset of an opaque datatype tagged WKB, or POINT with a column dataset of a compound datatype (§6.2).

  6. Each geometry column’s GEOMETRY_TYPES attribute has the Marquet geometry-type enumeration as its datatype and distinct elements, and it is either empty or lists the type of every non-null geometry in rows [0, NROWS). For a point column it is either empty or holds exactly the one type that matches the column’s coordinate fields (§6.3).

  7. Every WKB value of a geometry column is ISO WKB encoding exactly one geometry of the seven geometry types, without extensions or trailing bytes (§7.1).

  8. In every variable-length WKB column, the VALUES member is a rank-1 uint8 dataset without a user-defined fill value; every non-null row’s slice holds exactly one WKB value; and every null row is marked FALSE in the column’s MASK member (§7.2).

  9. In every fixed-length WKB column, every non-null element is exactly one WKB value occupying all of the element’s bytes, and the column’s fill value is not a valid WKB value (§7.3).

  10. In every point column, the compound fields are x, y, and optionally z and m, in that order and of one floating-point datatype; the fill value has no NaN field; and every non-null row is either an empty point with NaN in every field or a point whose x and y are not NaN (§7.4).

  11. Every CRS attribute is either a PROJJSON document or has a null dataspace (§8.1).

  12. Every EDGES and ORIENTATION attribute has the Marquet edges or orientation enumeration as its datatype, and every geometry column whose EDGES attribute is not PLANAR has a geographic coordinate reference system (§9.1, §9.2).

  13. In every geometry column that carries ORIENTATION, every polygon’s rings are ordered as that attribute states (§9.2).

  14. Every BBOX attribute has the fields and field order of §6.4, contains every non-null, non-empty geometry of its column, and has xmin no greater than xmax unless the column’s coordinate reference system is geographic.

  15. Every COVERING attribute references a covering column that satisfies §10.5, and no covering column is referenced by more than one geometry column.

  16. Every search-index dataset of kind BLOCK_BBOX or PACKED_RTREE carries the attributes its kind requires, is referenced only from the SEARCH_INDEX_LIST of a geometry column, and, when its validity check passes, correctly describes that column’s rows [0, NROWS) as specified in §10.3 and §10.4.

  17. No datatype defined by this specification is stored as a named datatype below the table group (§6.1).

A producer that modifies a Marquet table MUST follow §11 in addition to HEP001’s rules for writing.

13Reserved names

Marquet follows HEP001’s naming rules (HEP001 reserved names): names that carry contractual meaning are written in fixed-length uppercase ASCII with underscores between words, and attributes private to one kind of search index are written in lowercase snake_case. Under rule 7 of those rules, the names and tokens in the catalog below are scoped to Marquet tables. In a table group that carries MARQUET_VERSION, and in its columns, list column groups, and search-index datasets, each of them has the meaning that this specification gives it, and producers MUST NOT use it there for any other purpose, including as the name of a column, just as HEP001’s rule 2 provides for HEP001’s own names. In a table group without MARQUET_VERSION, the same names have no Marquet meaning: producers may use them for their own purposes, another layered HEP may define them differently, and a Marquet consumer MUST NOT interpret them. None of them collides with a name that HEP001 reserves.

MARQUET_VERSION itself is the exception, because it decides which table groups Marquet applies to. As HEP001’s rule 7 requires of every marker attribute, no other layered HEP may define it, and producers MUST NOT write it on a table group that is not a Marquet table (§5.1). A Marquet table MAY also carry the marker attribute of another layered HEP, but only as that rule allows: when the other HEP builds on Marquet, or when the two HEPs define no name or KIND value in common.

Three kinds of name are documented with the structures that define them but are not reserved, even in Marquet tables. The attributes private to one kind of search index (block_length on BLOCK_BBOX indexes, node_size and num_items on PACKED_RTREE indexes) fall under HEP001’s rule 5, which places such attributes outside the reserved-name contract. The member names of Marquet’s enumerations and the field names of its compound datatypes are parts of datatype definitions rather than names of HDF5 objects or attributes, so rule 7 does not reach them. None of these names constrains anything outside the datasets, attributes, and datatypes in which it appears.

13.1Reserved name catalog

Table group attributes
MARQUET_VERSION, GEOMETRY_COLUMNS.
Geometry column attributes
GEOMETRY_ENCODING, GEOMETRY_TYPES, CRS, EPOCH, EDGES, ORIENTATION, BBOX, COVERING.
GEOMETRY_ENCODING values
WKB and POINT, defined by this revision; LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, and MULTIPOLYGON, reserved for future native encodings.
Opaque datatype tag
WKB, the tag of the opaque datatype of a fixed-length WKB column.
KIND values
BLOCK_BBOX and PACKED_RTREE, both declared to apply to list columns.

14Mapping to and from GeoParquet

The tables below give the correspondence between GeoParquet and Marquet, in both directions, for GeoParquet 2.0 files, whose metadata is split between the native Parquet GEOMETRY and GEOGRAPHY logical types and the optional GeoParquet geo metadata. Non-geometry columns convert between Parquet and H5Col in the ordinary way and are not covered here.

14.1From GeoParquet to Marquet

Table 5:Converting GeoParquet 2.0 to Marquet

GeoParquet or Parquet itemMarquet counterpart
A file, or a tableA Marquet table group with MARQUET_VERSION
Geometry columns (columns keys, or columns of logical type GEOMETRY or GEOGRAPHY)GEOMETRY_COLUMNS
primary_columnGEOMETRY_COLUMNS[0]; without geo metadata, the first geometry column
WKB values in a BYTE_ARRAY columnGEOMETRY_ENCODING="WKB"; the values are copied unchanged into a variable-length WKB column, or into a fixed-length WKB column when all have one length
A null value in an optional columnA null geometry (§7.5)
A required columnA column without null geometries; a variable-length WKB column then needs no MASK
geometry_typesGEOMETRY_TYPES, by member name; without geo metadata, the union of the geospatial statistics’ type codes over all row groups, by member value, or empty when a row group lacks them. A code that is not a member value of Table 2 means the column cannot be converted
crs absent, and Parquet crs absentCRS absent
crs as PROJJSONCRS with the same PROJJSON document
crs: null, or Parquet crs of srid:0CRS with a null dataspace
Parquet crs as authority:code, srid:N, or projjson:key, without geo metadataCRS with the PROJJSON document resolved from it. A converter that cannot resolve the identifier MUST NOT guess; it either stops or, with its user’s agreement, records the coordinate reference system as unknown
edges absent or planar; Parquet GEOMETRYEDGES absent
edges other than planar; Parquet GEOGRAPHY with algorithmEDGES with the member of the same name
Parquet GEOGRAPHY without algorithm, and no edgesEDGES = SPHERICAL, Parquet’s default for GEOGRAPHY
orientation: "counterclockwise"ORIENTATION = COUNTERCLOCKWISE
bboxBBOX, with the array positions mapped to named fields according to the array’s length
epochEPOCH
covering.bbox and its struct columnCOVERING and a covering column, with the struct’s fields as compound fields in Marquet’s order, null values replaced by the covering column’s fill value, and the boxes of empty geometries replaced by the empty box
Per-row-group GeospatialStatisticsMay seed the boxes of a BLOCK_BBOX index when the row order is kept and every row group but the last has the same number of rows; the null counts can come from the column’s ordinary statistics, but the empty counts require a pass over the geometries
Feature identifier recorded in custom metadata (for example, gdal:schema)INDEX_COLUMNS[0]

14.2From Marquet to GeoParquet

Table 6:Converting Marquet to GeoParquet 2.0

Marquet itemGeoParquet or Parquet counterpart
A WKB geometry column, in either formA BYTE_ARRAY column holding the WKB values unchanged, with logical type GEOMETRY, or GEOGRAPHY when EDGES is not PLANAR; encoding: "WKB"
A point columnThe same, with each point encoded as ISO WKB, little-endian, and an empty point as a point whose coordinates are all NaN
A null geometryA null value in an optional column
GEOMETRY_COLUMNS[0]primary_column
GEOMETRY_TYPESgeometry_types, by member name; GeoParquet requires the list to be exact, so an exporter that cannot vouch for the attribute recomputes it from the geometries
CRS absentcrs absent; Parquet crs absent
CRS with a PROJJSON documentcrs with the same document; Parquet crs with the same document, or with an authority:code string taken from its id member
CRS with a null dataspacecrs: null; Parquet crs of srid:0
EDGES absent or PLANARedges absent or planar; Parquet GEOMETRY
EDGES other than PLANARedges, lowercased; Parquet GEOGRAPHY with the algorithm of the same name
ORIENTATIONorientation: "counterclockwise"
BBOXbbox, ordered as GeoParquet requires; GeoParquet’s bbox holds M bounds only together with Z bounds, so M bounds without Z bounds are dropped. An exporter that needs a tight box recomputes it
EPOCHepoch
COVERING and its covering columncovering.bbox and a struct column with the same fields, of Parquet type FLOAT or DOUBLE, holding a null value wherever the covering column holds its fill value
BLOCK_BBOX and PACKED_RTREE indexesNo counterpart; the Parquet writer computes row-group geospatial statistics itself
INDEX_COLUMNS[0]A feature identifier recorded in custom metadata, if the exporter wishes

14.3GeoParquet 1.0 and 1.1

GeoParquet 1.0 and 1.1 files carry all of their geospatial metadata in the geo metadata and store geometries as plain BYTE_ARRAY columns, so they convert to Marquet like a GeoParquet 2.0 file whose native logical types are absent. GeoParquet 1.1 also allowed geometries in native encodings derived from GeoArrow, as nested lists of coordinate structs. A converter maps the point encoding, a struct of x, y, and optionally z doubles, field for field to a point column, and encodes the other native encodings — linestring, polygon, multipoint, multilinestring, and multipolygon — as WKB, since this revision of Marquet has no native encodings for them.

15Worked examples

The examples use the compact notation of HEP001’s worked examples, listing each HDF5 object with its datatype, shape, and attributes.

15.1One point in three storage forms

The table below holds the same three rows — a point, a null geometry, and an empty point — in each of the three storage forms. A real table would use one form; the three side by side show how each expresses the same values. The point is POINT (-88.2434 40.1164) in longitude and latitude, and since no column carries a CRS attribute, its coordinate reference system is OGC:CRS84. Its 21-byte little-endian WKB value reads as follows:

01                        byte order: little-endian
01 00 00 00               geometry type 1 = Point
2b f6 97 dd 93 0f 56 c0   x = -88.2434
90 a0 f8 31 e6 0e 44 40   y =  40.1164
/stations                             (Group)
  CLASS             = "COLUMN_TABLE"  (ASCII, fixed length)
  VERSION           = "1.0"           (ASCII, fixed length)
  NROWS             = 3               (uint64, scalar)
  MARQUET_VERSION   = "1.0"           (ASCII, fixed length)
  GEOMETRY_COLUMNS  = [ref(location), ref(location_wkb), ref(location_wkb_list)]
  column-order      = ["location", "location_wkb", "location_wkb_list"]

/stations/location                    (Dataset, compound {x: float64, y: float64}, shape (3,))
  fill value        = {9.9692099683868690e+36, 9.9692099683868690e+36}
  GEOMETRY_ENCODING = "POINT"         (ASCII, fixed length)
  GEOMETRY_TYPES    = [Point]         (1-D, geometry-type enumeration)
  BBOX              = {xmin: -88.2434, ymin: 40.1164, xmax: -88.2434, ymax: 40.1164}
  values            = {-88.2434, 40.1164}, {9.9692e+36, 9.9692e+36}, {NaN, NaN}

/stations/location_wkb                (Dataset, opaque, 21 bytes, tag "WKB", shape (3,))
  fill value        = 46 49 4c 4c 01 02 03 … 11            ("FILL" and a byte ramp)
  GEOMETRY_ENCODING = "WKB"
  GEOMETRY_TYPES    = [Point]
  BBOX              = {xmin: -88.2434, ymin: 40.1164, xmax: -88.2434, ymax: 40.1164}
  values            = 01 01000000 2bf697dd930f56c0 90a0f831e60e4440   (the point)
                      46 494c4c01 0203040506070809 0a0b0c0d0e0f1011   (null: the fill value)
                      01 01000000 000000000000f87f 000000000000f87f   (empty: POINT (NaN NaN))

/stations/location_wkb_list           (Group)
  CLASS             = "LIST_COLUMN"   (ASCII, fixed length)
  KIND              = "OFFSETS"       (ASCII, fixed length)
  GEOMETRY_ENCODING = "WKB"
  GEOMETRY_TYPES    = [Point]
  BBOX              = {xmin: -88.2434, ymin: 40.1164, xmax: -88.2434, ymax: 40.1164}

/stations/location_wkb_list/OFFSETS   (Dataset, uint64, shape (4,))      values 0, 21, 21, 42
/stations/location_wkb_list/MASK      (Dataset, bool enum, shape (3,))   values TRUE, FALSE, TRUE
/stations/location_wkb_list/VALUES    (Dataset, uint8, shape (42,))
  bytes 0–20 hold the point; row 1 is null and occupies no bytes;
  bytes 21–41 hold POINT (NaN NaN)

15.2A table of lakes with spatial search indexes

This example is the table of §4. It describes N lakes in a projected coordinate reference system with Z coordinates. The shorelines are polygons and multipolygons stored as variable-length WKB, with a guaranteed winding order, a covering column, and both kinds of spatial search index. A second geometry column holds each lake’s centroid as a native point. The lake identifier serves as the feature identifier. No shoreline is null, so the shoreline column has no MASK member. Fill values are shown only for the geometry-related columns, and W is the total length of the shorelines’ WKB values.

/lakes                                (Group)
  CLASS             = "COLUMN_TABLE"
  VERSION           = "1.0"
  NROWS             = N
  GENERATION        = g
  MARQUET_VERSION   = "1.0"
  GEOMETRY_COLUMNS  = [ref(shoreline), ref(centroid)]
  INDEX_COLUMNS     = [ref(lake_id)]
  _index            = "lake_id"
  column-order      = ["lake_id", "name", "area", "shoreline",
                       "shoreline_bbox", "centroid"]

/lakes/lake_id                        (Dataset, uint64, shape (N,))
/lakes/name                           (Dataset, fixed UTF-8, shape (N,))
/lakes/area                           (Dataset, float64, shape (N,))
  units             = "m2"
  units_vocabulary  = "UDUNITS-2"

/lakes/shoreline                      (Group)
  CLASS             = "LIST_COLUMN"
  KIND              = "OFFSETS"
  GEOMETRY_ENCODING = "WKB"
  GEOMETRY_TYPES    = [Polygon Z, MultiPolygon Z]
  CRS               = '{"type": "ProjectedCRS", "name": "NAD83(CSRS) / UTM zone 20N", …,
                        "id": {"authority": "EPSG", "code": 2961}}'   (fixed-length UTF-8)
  ORIENTATION       = COUNTERCLOCKWISE                                 (orientation enumeration)
  BBOX              = {xmin: 237100.0, ymin: 4810400.0, zmin: 0.0,
                       xmax: 776800.0, ymax: 5235700.0, zmax: 520.0}
  COVERING          = ref(shoreline_bbox)
  SEARCH_INDEX_LIST = [ref(SEARCH_INDEXES/shoreline__block_bbox),
                       ref(SEARCH_INDEXES/shoreline__rtree)]
/lakes/shoreline/OFFSETS              (Dataset, uint64, shape (N+1,))
/lakes/shoreline/VALUES               (Dataset, uint8, shape (W,))

/lakes/shoreline_bbox                 (Dataset, compound {xmin, ymin, zmin, xmax, ymax, zmax: float64},
                                       shape (N,))
  fill value        = all fields 9.9692099683868690e+36

/lakes/centroid                       (Dataset, compound {x: float64, y: float64}, shape (N,))
  fill value        = {9.9692099683868690e+36, 9.9692099683868690e+36}
  GEOMETRY_ENCODING = "POINT"
  GEOMETRY_TYPES    = [Point]
  CRS               = (the same PROJJSON document as the shoreline column)

/lakes/SEARCH_INDEXES                 (Group)
/lakes/SEARCH_INDEXES/shoreline__block_bbox
                                      (Dataset, compound {xmin, ymin, zmin, xmax, ymax, zmax: float64,
                                       n, null_count, empty_count: uint64}, shape (ceil(N/B),))
  KIND              = "BLOCK_BBOX"
  block_length      = B               (uint64)
  SOURCE_GENERATION = g
  SOURCE_NROWS      = N
/lakes/SEARCH_INDEXES/shoreline__rtree
                                      (Dataset, compound {xmin, ymin, xmax, ymax: float64,
                                       index: uint64}, shape (T,))
  KIND              = "PACKED_RTREE"
  node_size         = 16              (uint16)
  num_items         = N               (uint64; every shoreline is non-null and non-empty)
  SOURCE_GENERATION = g
  SOURCE_NROWS      = N

With N = 10,000 lakes and B = 4,096, the block bounding-box index has three entries. With a node size of 16, the R-tree has levels of 10,000, 625, 40, 3, and 1 entries, T = 10,669 entries in all, with the root at position 0, the three entries of the next level at positions 1 to 3, and the leaves at positions 669 to 10,668.

16Relationship to HEP001

Marquet is a layered HEP in the sense of HEP001’s naming rule 7, with MARQUET_VERSION as its marker attribute: it gives H5Col table groups additional meaning without changing what they are. A Marquet table remains a conformant H5Col table, so every H5Col tool can read, copy, and validate it, although only a producer that implements this specification may append rows to it or update its geometries (§11). Marquet relies on HEP001 for all of the following:

Designing Marquet exposed two gaps in HEP001, and HEP001 was amended alongside this proposal to close them. First, HEP001 had forbidden list column groups to carry search indexes, which would have left variable-length WKB columns, the general storage form, without any spatial index. HEP001 now allows a list column to reference search indexes of kinds that are declared to apply to list columns (HEP001 search indexes over list columns), and Marquet declares both of its kinds so. Second, HEP001 had no rule for names defined by other HEPs. Nothing stopped a later revision of HEP001 from claiming a name such as CRS or BBOX for another purpose, and nothing said in which tables such a name has Marquet’s meaning. HEP001’s new naming rule 7 scopes the names that a layered HEP defines to the table groups that carry its marker attribute, here MARQUET_VERSION. Other tables and other layered HEPs remain free to use the same names, and later revisions of HEP001 will not define them. Both HEPs are drafts. This revision of Marquet requires the revision of HEP001 that includes both amendments, and HEP001’s version 1.0 will include them.

Other gaps that Marquet works around without changing HEP001 are worth recording for future revisions of both:

17Security considerations

The considerations of HEP001 (HEP001 security considerations) apply to Marquet tables. In particular, Marquet’s search indexes are derivative data that an adversary can falsify as easily as H5Col’s: a tampered block bounding box or R-tree can make a consumer skip rows that do satisfy a query. Consumers SHOULD offer a mode that verifies spatial indexes against the geometries or ignores them.

WKB values and PROJJSON documents read from a file are untrusted input. A WKB decoder MUST check every count it reads against the number of bytes that remain in the value, so that a value cannot make it read past its end or allocate memory far beyond the value’s size, and SHOULD limit the nesting of geometry collections. A consumer SHOULD limit the size and nesting depth of the PROJJSON documents it parses. A coordinate reference system library may be configured to download transformation grids from the network when it processes a coordinate reference system; consumers processing untrusted files SHOULD disable such network access.

Before descending a packed R-tree, a consumer MUST check that node_size is at least 2, that num_items does not exceed NROWS, and that the total number of entries T computed from them does not exceed the dataset’s extent. While descending, it MUST check that every internal entry’s index equals the position the layout prescribes and that every leaf’s index is a row position below NROWS. An index that fails any of these checks MUST be treated as invalid. Without these checks, a malformed tree could make the consumer read out of bounds or loop indefinitely.

18References