The geo:overviews format¶
tylertoo's overview files follow the geo:overviews specification:
COG-style multi-resolution overviews embedded in a GeoParquet file that
remains a fully valid GeoParquet 1.1 file. The full specification is
rendered below from its canonical source,
context/OVERVIEWS_SPEC.md
in the tylertoo repository, so this page and the repository file can't
drift apart.
Draft specification
geo:overviews is a draft. The format can still change in
response to implementation experience and GeoParquet community
review. The formal stability declaration — the compatibility
promise tylertoo 1.0 makes to files it has already written — is
not yet part of this document; it is tracked in
#441.
Status
- Spec: draft, version 0.2.0.
- Implementation: tylertoo writes spec version 0.2.0 (as of
tylertoo 0.8.0) and checks files against it with its
validatesubcommand. - Standardization: submission to the official GeoParquet process (opengeospatial/geoparquet) is pending — no submission has been filed yet. The remaining steps are tracked in #701.
GeoParquet Overviews Specification¶
Version: 0.2.0
Status: Draft — v0.1.0 design decisions (§11) APPROVED by maintainer
2026-07-02; the point-clustering (§12) and line-coalescing (§13)
extensions consolidated into this version 2026-07-03. Implementation
may proceed against this document.
License intent: CC BY 4.0
Standardization target: candidate official GeoParquet extension via
the opengeospatial/geoparquet process (the covering path into 1.1)
This document specifies COG-style multi-resolution overviews embedded in a GeoParquet file. It defines a physical layout, a footer metadata schema, and read/write protocols that let a client fetch only the resolution band it needs over HTTP range requests, while the file remains a fully valid GeoParquet 1.1 file readable by any existing tool.
It is designed for standardization through the official GeoParquet
spec process (opengeospatial/geoparquet): the metadata is structured
so it can be merged into the geo file metadata as a spec-level
extension (as covering was in 1.1), and it incubates under its own
footer key until then. The layout concepts of partitioning mode
(§2.3) are shared with the third-party COGP draft (Kanahiro); COGP
interop is an optional courtesy (§3.1), not a design constraint.
Two opt-in extensions are part of this version: point clustering (§12) and line coalescing (§13). Both are additive: a file that does not opt in is unaffected, and a file that does opt in remains a valid baseline overview file for readers that ignore the extension columns and metadata (§3.8).
The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, MAY, and OPTIONAL are to be interpreted as in RFC 2119. Sections and blocks labeled (informative) carry no conformance requirements; RFC 2119 key words appearing inside informative text describe requirements stated normatively elsewhere.
The v0.1.0 open questions were all resolved by maintainer review; the resolution record is kept in §11 (informative).
1. Scope and terminology¶
1.1 Purpose¶
Provide "COG for vector": a single self-describing GeoParquet file that carries several precomputed generalizations of a dataset at increasing levels of detail, so that a renderer can request a coarse overview cheaply and progressively refine, and an analyst can recover the exact source data with a single predicate.
1.2 Terms¶
- Overview file: a GeoParquet 1.1 file conforming to this spec.
- Level: a logical rendering-detail band, numbered
0(coarsest) toL-1(finest). A level is materialized as a contiguous run of Parquet row groups and identified in metadata. - GSD (ground sample distance): the approximate smallest ground distance, in meters, that is independently meaningful at a level. GSD governs which features/vertices survive at a level; it is not an accuracy guarantee. Always meters, regardless of the file CRS (§7.1).
- Canonical level: the single level whose rows reproduce the source
dataset value-for-value (§2.4). In
duplicatingmode this is the finest level; inpartitioningmode the union of all levels is canonical (§2.3). - Generalization: the process of producing a coarser rendering — feature thinning (dropping whole features), simplification (vertex reduction), and optional collapse (e.g. polygon → point).
- Covering: the GeoParquet 1.1
bboxcovering struct column (xmin, ymin, xmax, ymax) with per-row-group statistics, used for spatial pruning. - Row group (RG): a Parquet row group. RG indices are zero-based.
1.3 Relationship to prior art¶
- COGP (third-party draft; feature-level, no simplification,
prefix reads): this spec's
partitioningmode shares its concepts; we deliberately keep itsgsdandrow_group_endfield semantics so a compatibility key is cheap to emit (§3.1). - Cloud Optimized GeoTIFF / COPC: the
duplicatingmode borrows the COG read model — one level band is a complete, self-contained rendering of the whole dataset; a reader fetches exactly one band. - ESRI Spatially Optimized Parquet: rejected model (per-scale geometry columns). We use extra rows, not extra columns (§2.1).
2. Level model¶
2.1 Row model (normative)¶
Overview levels MUST be represented as additional rows, not additional columns. Every row belongs to exactly one level. A file MUST NOT encode levels as parallel geometry columns.
Rationale: extra rows keep the file a valid, flat GeoParquet table that any
SQL engine can filter with one predicate (WHERE level = k), and let
row-group pruning operate per level without schema games.
2.2 duplicating mode¶
Each level is a self-contained generalized rendering of the entire
dataset. A feature that is visible at level k typically also appears
(usually more generalized) at all coarser levels 0..k and (usually less
generalized) at all finer levels k..L-1. A reader selects and fetches
exactly one level band for a given display scale.
Constraints:
- Levels
0..L-1MUST be ordered coarse → fine. - The finest level (
L-1) is the canonical level (§2.4) and MUST reproduce the source data value-for-value. - A coarser level MAY contain fewer features than a finer level (thinning) and MAY contain simplified or collapsed geometries.
- Feature identity across levels is NOT required to be stable, and readers
MUST NOT assume a feature at level
kcorresponds 1:1 to a feature at levelk+1. (Cross-level joins are a non-goal; see §8.)
Features are duplicated across levels in this mode; it has no counterpart in the third-party COGP draft (which requires each feature in exactly one row group).
2.3 partitioning mode¶
Each feature appears exactly once, placed at its coarsest level —
the coarsest level at which it is still meaningful under that level's GSD.
No geometry is simplified or duplicated; only the assignment of features to
levels (and the physical ordering) differs from an ordinary file. A reader
fetches the prefix of levels 0..z for target level z.
Constraints:
- Every source feature MUST appear in exactly one row group, geometry preserved verbatim.
- Levels MUST be ordered coarse → fine and end on RG boundaries (§4).
- The union of all levels is the canonical dataset. There is no single
canonical level row-band; the canonical level pointer (§3.4) MUST be
nullin this mode, signalling "all rows, no filter" (§6.2).
A partitioning-mode file's layout is concept-compatible with the
third-party COGP draft; a writer MAY additionally emit the cogp
compatibility key (§3.1) to make it consumable by COGP readers. COGP
validity is NOT a conformance requirement of this spec.
2.4 Canonical fidelity (normative)¶
In duplicating mode, the geometry and all attribute values of every
canonical-level row MUST be value-identical to the corresponding source
row. "Value-identical" means: identical attribute values, and geometry
whose coordinates are bit-for-bit equal after decoding (no simplification,
no reprojection, no coordinate rounding beyond what the source already
contained). Physical encoding (compression, dictionary, page layout) MAY
differ.
Canonical fidelity is set/value equality only: row order within the canonical level is NOT required to match source order (the intra-level spatial sort, §4.3, reorders rows). Readers MUST NOT rely on canonical row order. (Resolved Q1, §11.)
2.5 Mode × mechanism applicability (informative)¶
The generalization mechanisms defined or referenced by this spec do not all apply in both modes. Normative rules live in the referenced sections; this table is the one-place summary:
| Mechanism | duplicating |
partitioning |
|---|---|---|
| Feature thinning / visibility gating (§1.2) | yes (rows dropped per level) | yes (governs assignment to a level; nothing is dropped, §2.3) |
| Ranking (which feature wins a cell / is assigned coarser) | yes | yes |
| Density budget (informative provenance, §3.5) | yes | yes |
| Geometry simplification / type collapse (§7.5) | yes (non-canonical levels only, §2.4) | no — geometry is verbatim (§2.3) |
| Point clustering (§12) | yes (opt-in) | no — MUST be rejected (§12.5) |
| Line coalescing (§13) | yes (opt-in) | no — inert or rejected per §13.5 |
3. Footer metadata¶
3.1 Key strategy (REVISED 2026-07-02)¶
Decision: incubate under our own footer key, designed for merger
into the official geo metadata as a GeoParquet spec extension.
- Key name:
geo:overviews(UTF-8 string), value is a UTF-8 JSON object with the schema in §3.2. Thegeo:-prefixed sibling key signals spec-track intent while keeping the incubating structure out of thegeoobject itself (so no reader can mistake it for ratified 1.1/2.0 metadata). Upon standardization, the object moves verbatim to anoverviewsmember of thegeometadata and the sibling key is dropped — the JSON schema is designed so this is a key rename only.
Rationale: this spec's standardization path runs through the official
opengeospatial/geoparquet process (the same path covering took into
1.1), not through third-party drafts. Structuring the metadata for
geo-merger from day one is what makes the eventual extension
proposal a rename rather than a redesign.
COGP interop (optional, informative). The partitioning-mode
layout is concept-compatible with the third-party COGP draft
(Kanahiro), and our levels schema deliberately keeps COGP's
row_group_end/gsd field semantics. A writer MAY additionally emit
a cogp footer key containing the COGP-subset fields
(version, levels[].{row_group_end, gsd}) for partitioning-mode
files so existing COGP readers can consume them. When both keys are
present the geo:overviews key is authoritative; a validator MUST
flag disagreement between them as an error. A COGP prefix-reader that
encounters a duplicating-mode file (via a MAY-emitted cogp key)
over-fetches — levels are self-contained, so a prefix is a superset —
but is never wrong; writers SHOULD nonetheless omit the cogp key in
duplicating mode to avoid advertising semantics COGP does not
define.
The cogp key MUST only be emitted behind an explicit writer option,
default off. The geo:overviews key name may still be refined with
the GeoParquet spec maintainers; implementations MUST therefore keep
it a single named constant so a rename is a one-line change.
(Resolved Q2, §11.)
3.2 JSON schema¶
{
"version": string, // REQUIRED, semver MAJOR.MINOR.PATCH
"levels": [Level], // REQUIRED, non-empty, coarse→fine
"mode": string, // OPTIONAL: "duplicating" | "partitioning"
"canonical_level": integer | null, // OPTIONAL (see §3.4)
"generalization": Provenance // OPTIONAL, informative (§3.5;
// two exceptions, §12.4/§13.4)
}
Level := {
"row_group_end": integer, // REQUIRED, inclusive, 0-based
"gsd": number, // REQUIRED, meters, > 0
"zoom": integer // OPTIONAL, Web Mercator z (§5.2)
}
All fields are defined by this spec. (version, levels,
levels[].row_group_end, and levels[].gsd also form the
COGP-compatible subset used by the optional cogp key, §3.1.)
3.3 levels constraints (normative)¶
levelsMUST be non-empty and ordered coarse → fine.row_group_endMUST be a JSON integer with0 <= row_group_end < num_row_groups, zero-based, and strictly monotonically increasing across the array. The final entry'srow_group_endMUST equalnum_row_groups - 1(levels cover all row groups, no gaps). Levelkspans row groups(levels[k-1].row_group_end + 1) .. levels[k].row_group_endinclusive (level 0 starts at RG 0).gsdMUST be a positive JSON number and MUST be strictly monotonically decreasing across the array (coarse→fine ⇒ larger→smaller meters).- If
zoomis present on any level it SHOULD be present on all levels, MUST be a non-negative integer, and MUST be strictly monotonically increasing (consistent with decreasinggsd).
3.4 mode and canonical_level¶
modeSHOULD be present. If absent, readers MUST assumepartitioning(the non-duplicating default: safest assumption for a reader, since treating a partitioning file as duplicating would drop data).canonical_level:- In
duplicatingmode it MUST be present and MUST equallen(levels) - 1(the finest level). A reader/analyst uses it to build the canonical predicate (§5.3, §6.2). - In
partitioningmode it MUST benull(or absent), meaning "the whole table is canonical; no level filter needed."
3.5 generalization provenance (informative, two exceptions)¶
An OPTIONAL object recording how each level was generalized, for
reproducibility and debugging. Every member except engine and
levels is OPTIONAL; readers MUST tolerate the absence of any
OPTIONAL member and MUST ignore unrecognized members (§3.8).
The block is informative — readers MUST NOT rely on it for
correctness — with exactly two exceptions: a clustering member
with enabled: true (§12.4) and a coalescing member with
enabled: true (§13.4) each assert normative structural facts about
the file that a validator MUST enforce.
The renamed_columns member records collisions between input column
names and the columns this format reserves. The rename is a property
of this file — a reserved column is authoritative here, so both must
coexist — and not of the data it came from. A consumer that does not
carry a reserved column forward MAY therefore publish the renamed
column under its recorded source name; it MUST NOT do so when that
name is already taken by a column it is publishing, nor when it has
already restored another column onto that name (two columns MAY be
renamed away from one reserved name, so the map's values need not be
distinct). Renderers of overview files (tylertoo's PMTiles export
drops level from tile properties and restores a source level) are
the motivating case.
This does not make renamed_columns normative, so the two exceptions
above stand: a reader that ignores the member entirely still produces
a valid result, merely one that keeps the renamed name. The MUST NOT
binds only a consumer that chooses to act on the member.
The zoom_ceiling member marks a partial file (implementation
note, informative: tylertoo writes one for the coarse job of a sharded
build, #541). Such a file holds only the levels at or coarser than the
recorded zoom; its finest level is an ordinary generalized level, so
§2.4 canonical fidelity does not hold for it even though
canonical_level still reads L-1 as §3.4 requires. A partial file is
therefore not a conforming duplicating file: a validator that
recognizes the member SHOULD report it as such, and a consumer that
recognizes it MUST NOT treat the file as a complete pyramid (in
particular, MUST NOT use its canonical_level for §5.3 analysis). A
reader that does not recognize the member cannot tell; producers
SHOULD treat partial files as intermediates, not as published
artifacts.
Provenance := {
"engine": string, // e.g. "tylertoo 0.6.0"
"gsd_base": number, // OPTIONAL: non-default tile-band base
// used to derive levels[].gsd (§5.2);
// absent when the default 1024 was used
"levels": [ { // parallel to top-level "levels"
"simplify_tolerance_m": number, // world-space tolerance, meters
"thinning_factor": number, // cell-winner factor
"visibility_gate_m": number, // min bbox-diagonal kept, meters
"geometry_types": [string] // union of kinds at this level
} ],
"cascade": boolean, // OPTIONAL: cascading simplification —
// each non-canonical level simplified
// from the next-finer level's output,
// not from canonical geometry; absent
// when levels were simplified from
// canonical geometry directly
"collapse": string, // OPTIONAL: below-tolerance polygon
// disposition when not the drop
// default: "point" (representative
// point, Q4 opt-in) or "square"
// (area-dithered ~1×GSD placeholder
// square, type-preserving); absent
// when collapsed polygons were dropped
"representation": [ { // OPTIONAL: zoom-band representation
"zooms": [number, number], // inclusive [lo, hi] zoom range
"repr": string // "point" | "square" | "geom"
} ], // absent when no bands were requested;
// levels[].geometry_types remains the
// normative per-level type signal
"ranking": Ranking, // OPTIONAL: cell-winner priority source
"entry_zoom": EntryZoom, // OPTIONAL: attribute-driven entry
// zoom ladder; absent when none
// applied
"density_drop": DensityDrop, // OPTIONAL: per-level feature budget
"clustering": Clustering, // OPTIONAL: point clustering (§12.4)
"coalescing": Coalescing, // OPTIONAL: line coalescing (§13.4)
"renamed_columns": { string: string, ... }
// OPTIONAL: JSON object map of
// OUTPUT column name → the SOURCE
// column name it was moved aside
// from, when an input column
// collided with a column this file
// reserves (`level`, and
// `point_count` / `coalesced_count`
// in the modes that append them).
// Absent when nothing was renamed.
"zoom_ceiling": integer // OPTIONAL: partial-file marker — the
// level plan reached finer than this
// zoom but only the levels at or
// coarser than it were written;
// absent on every complete file
}
Ranking := {
"mode": string, // e.g. "explicit-sort-key",
// "class-ranking", "size-fallback"
"column": string, // OPTIONAL: source column read
"ranks": { string: number, ... },
// OPTIONAL: JSON object map of class
// value → priority; higher priority
// wins, e.g. {"motorway": 5,
// "primary": 4, "residential": 2}
"unknown_rank": number // OPTIONAL: priority of unlisted values
}
EntryZoom := {
"mode": string, // "dense-rank" (derived from the
// column) or "explicit" (hand-placed)
"column": string, // column read, as named in this file
"step": integer, // OPTIONAL: zooms between rungs
// ("dense-rank" only)
"base_zoom": integer, // OPTIONAL: zoom of rung 0, the
// coarsest PLANNED level's zoom
// ("dense-rank" only)
"max_zoom": integer, // OPTIONAL: finest PLANNED level's
// zoom ("dense-rank" only)
"rung_count": integer, // rungs in the resolved ladder
"rungs": { string: integer, ... }
// OPTIONAL: JSON object map of column
// value → entry zoom, present when
// rung_count <= 64, e.g.
// {"5000": 2, "250": 5}
}
DensityDrop := {
"drop_rate": number, // each coarser level keeps 1/rate
"gamma": number, // spatial-fairness exponent (>= 1)
"supercell_gsd_factor": number // fairness neighborhood, × GSD
}
EntryZoom records an attribute-driven ladder that admits each
feature it places to the levels from its entry zoom inward. A feature
with entry zoom z is first admitted to the coarsest level whose zoom
is at least z, or to the finest level when no level qualifies. The
ladder exempts the features it places from the visibility gate and
cell-winner thinning. Later stages still apply. A feature that
simplifies below a level's tolerance can be dropped there, and so can
one that the density budget sheds. Ranking then
describes only the features the ladder leaves to ordinary thinning,
so a laddered file can carry "ranking": {"mode": "size-fallback"}
alongside an entry_zoom block. The block is absent when the ladder
placed no feature.
The block describes the ladder as resolved against the data, not as
requested. A requested rung that matches no value in the column is
dropped and does not appear. Each rungs key is the matched column
value, written as the shortest decimal that round-trips to the same
f64. A value stored as f32 therefore appears widened: 0.3
appears as "0.30000001192092896". A reader matches keys against
column values exactly.
A "dense-rank" ladder ranks the column's distinct finite values in
descending order. The value of rank r enters at
base_zoom + r * step, and a value whose zoom would pass max_zoom
gets no rung. Both zooms come from the planned levels, which can
differ from the footer's levels: an empty level is omitted from the
file, and a zoom ceiling cuts the finest levels. A reader can rebuild
a dense-rank ladder from these fields and the column even when
rungs is omitted.
Ranking, EntryZoom, and DensityDrop record which mechanism a
producer used (FYI, never a contract); Clustering and Coalescing are defined in
§12.4 and §13.4. cascade matters for reproducibility: with the same
levels[].simplify_tolerance_m, cascaded and non-cascaded producers
emit different coarse-level coordinates, so a producer that cascades
SHOULD record "cascade": true. It asserts no structural fact
(informative, no validator rule); canonical fidelity (§2.4) is
unaffected because the canonical level is never simplified. Producer-side default values for these mechanisms
are an implementation concern, not fixed by this spec (for tylertoo
they are documented in docs/OVERVIEW_TUNING.md, with the code
constants as the source of truth).
Writers conforming to v0.2.0 MUST serialize ranks as a JSON object
map as shown above (not an array of pairs). (Implementation note,
informative: tylertoo emits the object map as of PR #190; its reader
additionally accepts the legacy array-of-pairs shape written by
earlier versions.)
3.6 Example — duplicating mode¶
{
"version": "0.2.0",
"mode": "duplicating",
"canonical_level": 2,
"levels": [
{ "row_group_end": 1, "gsd": 9783.94, "zoom": 2 },
{ "row_group_end": 5, "gsd": 2445.98, "zoom": 4 },
{ "row_group_end": 14, "gsd": 611.50, "zoom": 6 }
],
"generalization": {
"engine": "tylertoo 0.6.0",
"levels": [
{ "simplify_tolerance_m": 4000, "thinning_factor": 4.0,
"visibility_gate_m": 9784, "geometry_types": ["Point","Polygon"] },
{ "simplify_tolerance_m": 1000, "thinning_factor": 2.0,
"visibility_gate_m": 2446, "geometry_types": ["Polygon"] },
{ "simplify_tolerance_m": 0, "thinning_factor": 1.0,
"visibility_gate_m": 0, "geometry_types": ["Polygon"] }
],
"ranking": { "mode": "size-fallback" },
"density_drop": { "drop_rate": 1.65, "gamma": 1.5,
"supercell_gsd_factor": 128.0 }
}
}
3.7 Example — partitioning mode¶
{
"version": "0.2.0",
"mode": "partitioning",
"canonical_level": null,
"levels": [
{ "row_group_end": 0, "gsd": 1000, "zoom": 6 },
{ "row_group_end": 3, "gsd": 500, "zoom": 7 },
{ "row_group_end": 12, "gsd": 100, "zoom": 9 }
]
}
If the writer also emits the optional cogp compatibility key
(§3.1), it contains the version and levels[].{row_group_end, gsd}
subset of the above.
3.8 Versioning¶
version is semver. Minor bumps MAY add optional fields but MUST NOT alter
existing semantics; major bumps MAY break. Readers MUST ignore unrecognized
fields and MUST NOT treat an unsupported MAJOR version as conforming.
4. The level column and physical layout¶
4.1 level column (normative)¶
Every overview file MUST contain a physical column:
- Name:
level - Type: Parquet
INT32(logical/annotated as a plain signed 32-bit integer). - Nullability: NOT NULL. Every row MUST have a level value.
- Domain:
0 .. len(levels)-1.
Writers MUST reject source data whose schema contains a column named
level under case-insensitive comparison (e.g. LEVEL, Level):
SQL engines commonly resolve identifiers case-insensitively, so a
case-colliding source column would silently shadow the overview
column in reader predicates. (V1 finding F2, 2026-07-02.)
Conformance requires column↔footer consistency: for every row group
r and every row in r, the row's level value MUST equal the level
k whose RG span (§3.3) contains r. Equivalently, all rows of a given
row group MUST share one level value, and that value MUST match the
footer's level assignment for that RG index. A validator MUST check this
(§6.2).
Rationale: the footer key serves footer-only readers (range-request
clients); the visible level column serves naive SQL readers (DuckDB,
pandas) that never parse the footer. Both MUST agree.
level SHOULD be dictionary/RLE-encoded (it is constant within each row
group, so this is nearly free).
4.2 Ordering (normative)¶
- Levels MUST be written coarse → fine (level 0's row groups first).
- Each level MUST end exactly on a row-group boundary; a row group MUST NOT contain rows from two levels.
- The mapping from level to its RG range MUST match the footer
levelsarray (§3.3).
4.3 Intra-level spatial sort (SHOULD)¶
Within each level, features SHOULD be spatially clustered so that row-group bbox statistics prune well — Hilbert-curve order is RECOMMENDED.
Input contract: the writer assumes gpio-optimized / Hilbert-sorted input GeoParquet and preserves that order within each level. If the input is not sorted, the writer MAY sort per level; if it does neither, the file is still conformant but prunes poorly.
4.4 Covering column (normative for GeoParquet 1.1)¶
- The file MUST declare GeoParquet 1.1
bboxcovering metadata atgeo.columns.<primary>.covering.bbox. - The covering MUST be a struct column with
xmin, ymin, xmax, ymaxchild fields, each carrying per-row-group min/max statistics. - Row-group statistics on the covering column are REQUIRED — they are the spatial-pruning index (§5.1). Producers MUST NOT strip them.
4.5 Encoding recommendations¶
- Compression: ZSTD is RECOMMENDED for all columns.
- Geometry (WKB) and the bbox covering child columns MUST NOT use dictionary encoding (high-cardinality; dictionary hurts and bloats).
- The
levelcolumn SHOULD use RLE/dictionary. - Row-group size SHOULD be tuned for bounded-latency range fetches; coarse levels SHOULD be kept small enough to serve as a quick overview (the first level SHOULD be a small number of row groups).
5. Read protocols¶
5.1 Rendering (progressive display)¶
Given a viewport at display scale, a renderer:
- Display scale → target GSD. Compute the ground distance per screen
pixel for the current viewport (meters/pixel), optionally biased by a
quality factor. This is
target_gsd. - Level selection (mode-dependent):
duplicating: select the finest single level whosegsd >= target_gsd; if none qualifies (zoomed in past the finest level), select the finest levelL-1; if the target is coarser than level 0, select level 0. Fetch only that one level band (its RG range).partitioning: select the target levelzby the same rule, then fetch the prefix of row groups0..levels[z].row_group_end(levels accumulate).- bbox pruning. Intersect the viewport bbox with each candidate row group's covering min/max statistics; drop non-intersecting row groups.
- Byte ranges. From the Parquet footer, map surviving row groups to file byte offsets and issue HTTP range requests (or object-store partial reads). Within fetched row groups, apply a per-feature bbox predicate to discard features outside the viewport.
On zoom-in in partitioning mode, only additional row groups are fetched;
prior reads remain valid (features are not duplicated). On zoom change in
duplicating mode, a different single band is fetched (prior band may be
discarded).
5.2 GSD / zoom mapping (Web Mercator)¶
For Web Mercator target zoom z (z0..z16), GSD in meters is:
gsd(z) = 40075016.69 / 1024 / 2^z
(40075016.69 m = Web Mercator equatorial circumference; 1024 = assumed tile-band reference. This matches the cogp-rs convention.) Reference values:
| z | gsd (m) | z | gsd (m) | |
|---|---|---|---|---|
| 0 | 39135.76 | 5 | 1223.0 | |
| 1 | 19567.88 | 6 | 611.50 | |
| 2 | 9783.94 | 7 | 305.75 | |
| 3 | 4891.97 | 8 | 152.87 | |
| 4 | 2445.98 | 9 | 76.44 |
For geographic (degree) CRS inputs, GSD is still expressed in meters using
METERS_PER_DEGREE = 111320 to convert a degree-space target into meters
(§7.1). GSD is always meters in the footer regardless of file CRS.
5.3 Analysis (one-predicate rule)¶
The whole point of the visible level column is that analytical access is
a single predicate:
duplicatingmode: to recover the exact source dataset, filter to the canonical level:WHERE level = <canonical_level>. No other level's rows are canonical, so this both deduplicates and yields verbatim data.partitioningmode: no filter needed. The whole table (all levels) is the canonical dataset, each feature once.SELECT * FROM filereturns the source set.
Example DuckDB SQL:
-- duplicating mode: exact source data (canonical_level = 2)
SELECT * FROM 'overview.parquet' WHERE level = 2;
-- duplicating mode: a coarse overview for a bbox at level 0
SELECT * FROM 'overview.parquet'
WHERE level = 0
AND bbox.xmin < :xmax AND bbox.xmax > :xmin
AND bbox.ymin < :ymax AND bbox.ymax > :ymin;
-- partitioning mode: exact source data (no level filter)
SELECT * FROM 'overview.parquet';
-- partitioning mode: coarse overview = prefix of levels
SELECT * FROM 'overview.parquet' WHERE level <= 0;
DuckDB with the parquet reader will use row-group level and bbox
statistics to skip row groups, so these predicates prune at the storage
layer.
6. Writer conformance and validation¶
6.1 Writer MUST¶
- Produce a valid GeoParquet 1.1 file (valid
geometadata, primary geometry column, WKB or native encoding). - Declare the
bboxcovering column with per-RG min/max stats (§4.4). - Write levels coarse→fine, each ending on a row-group boundary (§4.2).
- Write a NOT NULL
INT32levelcolumn consistent with the footer (§4.1). - Write the
geo:overviewsfooter key with valid JSON per §3, including strictly increasingrow_group_end(final =num_row_groups-1) and strictly decreasinggsd. - In
duplicatingmode: setmode, setcanonical_level = L-1, and guarantee canonical-level value-identity to source (§2.4). - In
partitioningmode: place each feature exactly once with geometry verbatim, setcanonical_level = null. (Optionally emit thecogpcompatibility key per §3.1.) - Use ZSTD; no dictionary on geometry/bbox columns (§4.5).
6.2 Validation checklist (feeds validate subcommand, task P4)¶
A validator MUST check:
- [ ] File opens as valid GeoParquet 1.1 (geo metadata present, primary column declared).
- [ ]
geo:overviewsfooter key present, parses as UTF-8 JSON,versionis semver, MAJOR is supported. - [ ] If a
cogpcompatibility key is also present, itslevels[].{row_group_end, gsd}agree exactly withgeo:overviews(disagreement is an error, §3.1). - [ ]
levelsnon-empty;row_group_endstrictly increasing, 0-based, final ==num_row_groups - 1. - [ ]
gsdstrictly decreasing, all > 0. - [ ] If
zoompresent, strictly increasing and consistent withgsd. - [ ]
levelcolumn exists, is INT32, NOT NULL, domain0..L-1. - [ ] Column↔footer consistency: every row group's rows share one
levelvalue equal to the footer-derived level for that RG index (§4.1). - [ ] Covering
bboxstruct column present with per-RG min/max stats. - [ ]
modevalid; ifduplicating,canonical_level == L-1; ifpartitioning,canonical_levelis null/absent. - [ ] If
generalization.clustering.enabledis true: mode isduplicating, and the namedpoint_countcolumn satisfies §12.2 (INT64 NOT NULL, all values >= 1, canonical band all 1) (§12.4). - [ ] If
generalization.coalescing.enabledis true: the namedcoalesced_countcolumn satisfies §13.2 (INT32 NOT NULL, all values >= 1, canonical band all 1) (§13.4). - [ ] No dictionary encoding on geometry/bbox columns (SHOULD warn, not fail).
- [ ] Antimeridian: no geometry bbox spans the full lng range in a way that defeats pruning (§7.2) (SHOULD warn).
A validator MAY additionally compute quality metrics (row-group touch counts per viewport, prefix latency, per-level feature/vertex counts, clustering quality). These are informative.
Semantic correctness of the generalization (is level 0 actually a good coarse rendering?) is a producer responsibility; the validator checks structure, not cartographic quality.
7. Constraints and edge cases¶
7.1 CRS handling¶
- GSD is ALWAYS meters. For Web Mercator (EPSG:3857) data, use §5.2
directly. For geographic (EPSG:4326) data, convert degree tolerances to
meters with
METERS_PER_DEGREE = 111320(equatorial degree length) when deriving/comparing GSD. - The file's declared CRS (in
geometadata) is authoritative for the geometry coordinates; the meter-denominated GSD is an independent rendering hint and does NOT reproject geometry. - Latitude distortion:
METERS_PER_DEGREE = 111320is an equatorial approximation. This is acceptable for level selection (a coarse heuristic) but producers SHOULD document that high-latitude datasets see GSD/scale skew.
Overview files MUST use EPSG:4326 or EPSG:3857 (the two CRSs COGP handles); arbitrary projected-meter CRSs are deferred to a future version. (Resolved Q3, §11.)
7.2 Antimeridian¶
- Geometries MUST NOT cross the antimeridian in a way that breaks bbox pruning (a feature whose bbox spans ~360° of longitude defeats the covering index). Producers SHOULD split such geometries before writing.
- This mirrors COGP's prohibition and is REQUIRED for both modes.
7.3 Empty levels¶
- A level MUST contain at least one row group and at least one row. Empty
levels are NOT allowed (they would violate the strictly-increasing
row_group_endinvariant and waste a band). - If generalization would empty a level (e.g. every feature dropped below the visibility gate), the writer MUST either merge it into the adjacent coarser level or omit it entirely (renumbering levels).
- Omitting empty coarse levels is expected and normal, not an edge case:
a dataset of small features (e.g. country-scale buildings) is legitimately
invisible at world scales, so its pyramid simply starts at a finer GSD
than a producer's requested range. Readers MUST NOT assume level 0
corresponds to any particular zoom or GSD — the
levels[].gsd(and optionalzoom) values are the only source of truth for a level's scale.
7.4 Single-level degenerate files¶
- A file with
len(levels) == 1is conformant. It is an ordinary GeoParquet file plus ageo:overviewskey and a constantlevel = 0column. Induplicatingmodecanonical_level = 0. This is the degenerate "no overviews" case and is explicitly allowed.
7.5 Geometry types per level¶
- Generalization MAY change geometry type at coarser levels: a polygon MAY collapse to a point, a multipolygon MAY drop to a single polygon, etc. Recommendation: allow it. Coarse levels commonly render small polygons as points.
- If any level's geometry type differs from the primary declared type, the
file MUST list the union of geometry types actually present. Per
GeoParquet 1.1,
geo.columns.<primary>.geometry_typesMUST enumerate all types present anywhere in the column (e.g.["Point","Polygon"]). - The optional
generalization.levels[].geometry_typesblock (§3.5) SHOULD record the per-level union for tooling that wants to pick a renderer per band.
Geometry-type collapse MUST be opt-in for producers, default off
(default behavior: preserve type, drop below the gate instead of
collapsing) — renderers should not be surprised by a geometry type
switching mid-zoom unless the producer asked for it. When enabled it
SHOULD be documented via the generalization provenance (§3.5).
(Resolved Q4, §11.)
Two producer behaviors are conforming refinements of this rule:
- Zoom-banded collapse: type collapse MAY be unconditional within a
contiguous coarse zoom band (every polygonal feature of the band's
levels carried as its representative point) rather than tied to the
visibility gate — e.g. levels z0–7 all points, z8+ full geometry, in
one file. This is still Q4 geometry-type collapse and therefore still
opt-in; the bands SHOULD be recorded in
generalization.representation(§3.5) and the type union rules above apply unchanged. - Placeholder squares (below-tolerance polygons replaced by small
synthetic squares, e.g. area-dithered ~1×GSD squares at the
representative point) are not geometry-type collapse: the emitted
geometry is still a Polygon,
geometry_typesis unchanged, and the Q4 opt-in requirement does not apply. Producers SHOULD still record the disposition (generalization.collapse: "square", §3.5) because the coordinates are synthetic.
8. Non-goals¶
This spec explicitly does NOT address:
- Topology preservation across features (shared borders staying coincident after independent simplification). Each feature is generalized independently; slivers/gaps at coarse levels are expected.
- Cross-level feature identity / joins. A feature at level
kis not guaranteed to map to a specific feature at levelk+1. - In-place updates. Overview files are treated as immutable build artifacts; editing means regenerating.
- Multi-layer / multi-dataset files. One dataset per file. Layering is an application concern.
- New geometry encodings, CRS models, or tile-matrix-set semantics.
- Mandating a specific thinning/simplification algorithm. The spec constrains layout and metadata; the generalization engine is implementation-defined (tylertoo supplies one).
- Topology-aware network merging beyond §13. Line coalescing (§13) chains touching segments; it does not build a routable graph or guarantee planarity.
Attribute aggregation of dropped features — a v0.1.0 non-goal — is now specified as the opt-in point-clustering extension (§12).
9. Worked example (3-level duplicating file) (informative)¶
A tiny buildings dataset, generalized into 3 levels at z2/z4/z6, written
duplicating. 15 row groups total.
9.1 Footer geo:overviews JSON¶
{
"version": "0.2.0",
"mode": "duplicating",
"canonical_level": 2,
"levels": [
{ "row_group_end": 1, "gsd": 9783.94, "zoom": 2 },
{ "row_group_end": 5, "gsd": 2445.98, "zoom": 4 },
{ "row_group_end": 14, "gsd": 611.50, "zoom": 6 }
]
}
9.2 Row-group table¶
| level | zoom | RG indices | rows/RG (approx) | bbox extent (lng/lat) |
|---|---|---|---|---|
| 0 | 2 | 0–1 | ~200 | full dataset, heavily thinned + simplified |
| 1 | 4 | 2–5 | ~500 | full dataset, moderately generalized |
| 2 | 6 | 6–14 | ~800 | full dataset, canonical (verbatim source) |
Each level's row groups are Hilbert-sorted, so RG-level bbox extents form a
spatial tiling of the full dataset extent (e.g. RG 6 covers the
south-west quadrant, RG 14 the north-east, etc.). Level 0 (row_group_end
= 1) is the small quick-overview band; level 2 ends at RG 14 =
num_row_groups - 1.
9.3 Queries a z2 renderer issues¶
Target zoom 2 → target_gsd ≈ 9783.94 → duplicating selects level 0
(finest level with gsd >= target_gsd). Fetch band = RG 0–1, pruned by
viewport bbox:
-- Renderer at z2, viewport bbox (minx,miny,maxx,maxy)
SELECT geometry, name
FROM 'buildings_overview.parquet'
WHERE level = 0
AND bbox.xmin < :maxx AND bbox.xmax > :minx
AND bbox.ymin < :maxy AND bbox.ymax > :miny;
Over HTTP the client would instead read the footer, map level 0 → RG 0–1 byte offsets, prune RG 0/1 by covering stats, and range-request only the surviving row groups.
9.4 Query an analyst issues¶
Recover the exact source dataset (canonical level = 2):
SELECT * FROM 'buildings_overview.parquet' WHERE level = 2;
-- row count == source row count; geometry + attributes verbatim
10. Forward compatibility: GeoParquet 2.0 / native GEOMETRY¶
GeoParquet 2.0 introduces a Parquet-native GEOMETRY/GEOGRAPHY logical
type with native geometry statistics (min/max bbox carried by Parquet
itself), removing the need for a separate covering column.
Migration plan:
- Covering → native stats. In a 2.0 overview file, the
bboxcovering struct column (§4.4) is REPLACED by native geometry column statistics. All spatial-pruning language in §5.1 applies unchanged, sourcing min/max from the native geometry column's Parquet statistics instead of the covering column. Producers targeting 2.0 SHOULD omit the covering column. - Footer key unchanged. The
geo:overviewskey and its JSON schema (§3) are encoding-agnostic and carry over verbatim (row_group_end,gsd,mode,canonical_level,zoom) — until/unless the object is merged into thegeometadata by the official spec process, at which point it is a key rename only. levelcolumn unchanged. Still an INT32 NOT NULL column.- Version bump. A 2.0 overview file uses
versionMINOR/MAJOR bump and declares GeoParquet 2.0 in itsgeometadata; validators MUST accept native stats in place of the covering column when the file declares 2.0. - Dual-target period. During transition, a producer MAY emit BOTH the covering column AND native stats (redundant but maximally compatible).
During the transition, GeoParquet 2.0 overview files dual-emit the bbox covering column alongside native geometry stats (item 5 above); the covering column is dropped once 2.0 reader support is widespread. (Resolved Q5, §11.)
11. v0.1.0 open questions — resolution record (informative)¶
All v0.1.0 open questions were resolved by maintainer review 2026-07-02. The resolutions below are folded into the normative text of the sections they cite; this section is kept as the decision record.
- Q1 (§2.4) APPROVED: canonical fidelity is set/value equality only, not row order (Hilbert sort reorders rows within levels).
- Q2 (§3.1) APPROVED: incubation key is
geo:overviews, designed for verbatim merger into thegeometadata via the official GeoParquet spec process; optionalcogpcompatibility key emitted only behind an explicit writer flag, default off. Key name may still be refined with the GeoParquet spec maintainers — implementations MUST keep it a single named constant. - Q3 (§7.1) APPROVED: v0.1 restricts CRS to EPSG:4326 and EPSG:3857; arbitrary projected-meter CRS deferred.
- Q4 (§7.5) APPROVED: geometry-type collapse is opt-in, default off (default: preserve-type-or-drop).
- Q5 (§10) APPROVED: GeoParquet 2.0 files dual-emit the bbox covering column alongside native geometry stats during transition.
- Q6 (new) ~~
gsd(z)uses the constant1024; confirm this matches the cogp-rs reference exactly.~~ RESOLVED (verified against cogp-rs src/convert.rs, 2026-07-02): cogp-rs auto-derivesgsd(i) = 40_075_016 / (base · 2^i)with defaultbase = 1024(chosen as ~4× a 256-px tile so sub-pixel features drop; the base is a CLI-configurable knob, and--gsdoverrides derivation entirely). §5.2 table is correct as written. - Q7 APPROVED:
modestays SHOULD with documentedpartitioningdefault (the safe reader assumption), not REQUIRED.
12. Point clustering extension¶
An OPTIONAL, additive extension. A file written with clustering
remains a valid baseline overview file: the extension adds one column
(§12.2) and one provenance member (§12.4) that readers unaware of
this section ignore per §3.8. The normative requirements below apply
only to files that opt in (i.e. files whose footer carries a
generalization.clustering member with enabled: true).
12.1 Model (normative)¶
Clustering applies to point features only (Point/MultiPoint rows;
lines and polygons are unaffected) and to duplicating mode only
(§12.5). It MUST be opt-in for producers, default off (consistent
with the opt-in-collapse posture of §7.5 / Q4).
At each level, the generalization engine's per-cell survivor absorbs the point features that were thinned out of its cell at that level's grid, instead of them simply vanishing:
- Accounting (strict). At every level, every source point feature
MUST be counted in the
point_countof exactly one point row of that level: its own row if it survives the level, otherwise the row of the winner that absorbed it. A feature whose cell winner is itself removed after absorption (e.g. by a density budget) MUST be re-assigned to a surviving point row of the same level — which survivor is implementation-defined (tylertoo: the nearest) — so no feature is ever dropped from the count or counted twice. - Sum invariant (normative). Consequently, at every level, the
sum of
point_countover the level's point rows MUST equal the total source point-feature count exactly, including when a density budget or any other post-thinning drop mechanism is active. The clusters of a level partition the source point set at that level's grid. - Absorption is per level: a feature absorbed at level
kMAY itself be a row (with its own, smaller cluster) at levelk+1. - The winner keeps its own geometry and its own values for every non-accumulated column. (DIVERGENCE from supercluster, which re-centers a cluster at its members' centroid: keeping the winner's geometry is deterministic and anchors the cluster at a real feature.)
12.2 point_count column (normative when clustering)¶
A clustered file MUST contain a physical column:
- Name:
point_count(recorded in metadata, §12.4, so readers need not hard-code it). - Type: Parquet
INT64. Nullability: NOT NULL. - Domain:
>= 1. Non-point rows and unclustered (singleton) point rows carry1. - At the canonical level every value MUST be
1(clusters would violate canonical value-identity, §2.4 — the canonical band remains verbatim source data plus the constantpoint_count = 1).
Writers MUST reject source data whose schema already contains a column
named point_count under case-insensitive comparison (same rationale
as the level column rule, §4.1).
12.3 Attribute accumulation (normative when used)¶
A writer MAY additionally aggregate numeric columns across each
cluster (the tippecanoe --accumulate-attribute convention): the
winner's value of the column becomes sum | max | min | mean
over itself plus the absorbed features at that level. A writer that
does so MUST observe the following:
- Aggregates MUST be computed per level from source values (never
from a coarser level's already-aggregated values), so
meanis exact at every level. - Null member values do not contribute; a cluster with no non-null member keeps the winner's null.
- Canonical-level values remain verbatim (§2.4; every canonical cluster is a singleton).
- The aggregated column keeps its declared type; producers SHOULD
prefer floating-point columns for
mean.
12.4 Metadata (normative when clustering)¶
Clustering is recorded in the generalization provenance block
(§3.5) as its Clustering member:
Clustering := {
"enabled": true,
"point_count_column": string, // e.g. "point_count"
"accumulated": [ { "column": string,
"op": "sum"|"max"|"min"|"mean" } ] // OPTIONAL
}
This member is one of the two exceptions to §3.5's informative rule:
when clustering.enabled is true a validator MUST enforce §12.2
structurally — the file's mode is duplicating (§12.5), the named
column exists as INT64 NOT NULL, all values are >= 1 (checkable via
row-group statistics), and the canonical band's values are exactly
1.
12.5 Partitioning mode is excluded (normative)¶
A producer MUST reject a request to cluster a partitioning-mode
file. Rationale: a partitioning feature has exactly one row but
is displayed at every zoom from its level onward (prefix reads,
§2.3). A single stored point_count can only describe one level's
grid, so it would be wrong at every other display zoom; worse, a
feature absorbed at a coarse level reappears as its own row in a
finer band while remaining counted in the coarse winner, so
Σ point_count over any prefix double-counts. There is no
non-contorted single-column encoding of per-level counts in a
feature-once layout; producers who need clustering use duplicating
mode.
12.6 Implementation note (informative)¶
tylertoo implements this extension behind the opt-in --cluster
flag, with optional accumulation via --accumulate-attribute COL:OP;
enabling clustering also changes the engine's point-thinning default
(a sparser grid is harmless when losers are summarized rather than
discarded). Engine defaults and tuning guidance live in
docs/OVERVIEW_TUNING.md; they are implementation choices, not
requirements of this spec.
13. Line coalescing extension¶
An OPTIONAL, additive extension. A file written with coalescing
remains a valid baseline overview file: the extension adds one column
(§13.2) and one provenance member (§13.4) that readers unaware of
this section ignore per §3.8. The normative requirements below apply
only to files that opt in (i.e. files whose footer carries a
generalization.coalescing member with enabled: true).
13.1 Model (normative)¶
Coalescing applies to line features only (LineString rows; points
and polygons are unaffected, and MultiLineString rows pass through
unmerged) and to duplicating mode only (§13.5). Whether a
producer enables it by default is an implementation choice (§13.6);
the partitioning-mode behavior of a default-on producer is
constrained by §13.5.
At each non-canonical level, BEFORE the visibility gate and cell-winner thinning run, the generalization engine chains touching compatible line segments into single "stroke" LineStrings:
- Compatibility: segments MUST chain only within the same class value of the active class-ranking column (when one is active); with no class ranking, all lines are compatible. Null class values are compatible with each other only.
- Chaining: endpoints are matched exactly first, then chain ends
within a snap tolerance of
snap_tolerance_gsd_factor × gsd(§13.3). Nodes of degree 2 always continue a chain; junctions (three or more compatible endpoints) MUST terminate chains by default, preserving network topology. An implementation MAY optionally continue, through a junction, the pair(s) of lines that best continue each other within a bounded angular deviation from straight (stroke building). - Attributes: the merged row carries the attribute values of its highest-priority member (the same priority order the cell-winner stage uses), plus the member count (§13.2).
- The visibility gate and thinning then evaluate the merged chains, so a chain of individually sub-visibility fragments survives as one visible artery. This ordering is the point of the extension.
The canonical level MUST NOT be coalesced (§2.4 value fidelity:
the canonical band remains verbatim source rows plus the constant
coalesced_count = 1).
Feature identity across levels is already NOT guaranteed (§2.2), so a merged chain at a coarse level needs no correspondence machinery.
13.2 coalesced_count column (normative when coalescing)¶
A coalesced file MUST contain a physical column:
- Name:
coalesced_count(recorded in metadata, §13.4). - Type: Parquet
INT32. Nullability: NOT NULL. - Domain:
>= 1. Non-line rows and unmerged line rows carry1. - At the canonical level every value MUST be
1.
Writers MUST reject source data whose schema already contains a column
named coalesced_count under case-insensitive comparison (same
rationale as the level column rule, §4.1).
13.3 Snap tolerance (normative)¶
The snap tolerance is expressed in GSD multiples so it scales with
each level's resolution: two endpoints closer than one ground sample
are indistinguishable at the level, so 1.0 is the natural default.
0 restricts chaining to exact endpoint matches. Implementations MAY
realize the snap by grid quantization of endpoints (deterministic,
O(n)); the effective join distance then lies between 0 and √2 × the
tolerance.
13.4 Metadata (normative when coalescing)¶
Coalescing is recorded in the generalization provenance block
(§3.5) as its Coalescing member:
Coalescing := {
"enabled": true,
"snap_tolerance_gsd_factor": number, // snap tolerance, × GSD (§13.3)
"junction_angle": number, // degrees; 0 = strict degree-2
// chaining (junction
// continuation off, §13.1)
"max_level_rows": integer, // per-level candidate-line
// ceiling; levels with more
// candidate lines were written
// uncoalesced (counts all 1)
"coalesced_count_column": string, // e.g. "coalesced_count"
"merged": boolean // OPTIONAL: whether any chain
// joined >= 2 segments at any
// planned non-canonical level
// (including levels a
// `zoom_ceiling` did not write)
}
merged is informative: it lets a consumer decide whether the
coalesced_count column carries information without scanning it, and
— unlike the column's statistics — it describes the whole conversion,
so a partial file (§3.5 zoom_ceiling) agrees with the complete file
it is a prefix of. Readers MUST tolerate its absence.
All four parameter members carry the same weight: a writer conforming
to v0.2.0 that emits the coalescing block MUST include
snap_tolerance_gsd_factor, junction_angle, max_level_rows, and
coalesced_count_column, so the coalescing generalization is
reproducible from the file alone. Readers MUST tolerate the absence
of junction_angle and max_level_rows on files written before
these members existed (treating them as unknown, not as any default).
This member is one of the two exceptions to §3.5's informative rule:
when coalescing.enabled is true a validator MUST enforce §13.2
structurally — the named column exists as INT32 NOT NULL, all values
are >= 1 (checkable via row-group statistics), and the canonical
band's values are exactly 1.
(Implementation note, informative: tylertoo emits all four parameter members as of PR #190.)
13.5 Partitioning mode is excluded (normative)¶
Coalescing MUST NOT be applied to partitioning-mode files. Rationale:
partitioning requires every source feature to appear exactly once
with its geometry verbatim (§2.3). A merged chain is a new geometry
replacing several source rows — it cannot satisfy geometry-verbatim,
and removing the merged members from the finer bands they belong to
would break the prefix-read contract (a prefix would no longer be a
complete coarse rendering plus refinements). No sound construction was
found; producers who need coalescing use duplicating mode.
A tool whose coalescing option is enabled by default MUST treat a
partitioning conversion as non-coalesced (no coalesced_count column,
no coalescing provenance) rather than fail; it SHOULD reject an
explicit request to coalesce a partitioning conversion.
13.6 Implementation note (informative)¶
tylertoo enables coalescing by default (opt-out via
--no-coalesce-lines) with junction continuation off (strict
degree-2 chaining, per maintainer render review) and a snap tolerance
of 1.0 GSD. To bound memory, levels whose candidate line count
exceeds a ceiling skip coalescing while still emitting the
coalesced_count column (all 1) and the provenance block, keeping
the schema stable — such a file is conformant, since §13.2 permits
all-1 values, and the ceiling itself is recorded as
max_level_rows (§13.4). Engine defaults and tuning guidance live in
docs/OVERVIEW_TUNING.md; they are implementation choices, not
requirements of this spec.
14. Changelog¶
- Unreleased (additive, informative):
generalization.zoom_ceilingmarks a partial file (§3.5), andcoalescing.mergedrecords whether coalescing merged anything across the whole level plan (§13.4).generalization.entry_zoomrecords an attribute-driven entry-zoom ladder (§3.5). All OPTIONAL; files without them are unchanged. - v0.2.0 (2026-07-03): added the point clustering extension (§12:
point_countcolumn, numeric attribute accumulation,generalization.clusteringmetadata, duplicating-mode-only rule) and the line coalescing extension (§13:coalesced_countcolumn, degree-2 endpoint chaining with OPTIONAL angle-bounded junction continuation,generalization.coalescingmetadata, duplicating-mode-only rule with default-on-tool behavior); documented the post-v0.1.0 provenance members in §3.5 (gsd_base,ranking,density_drop,clustering,coalescing) and updated the examples (§3.6, §3.7, §9.1) to match; added the mode × mechanism table (§2.5) and the extension items in the validation checklist (§6.2); moved attribute aggregation out of the §8 non-goals (now §12); folded the approved Q1–Q5 resolutions into the normative text of §2.4, §3.1, §7.1, §7.5 and §10, converting §11 into an informative resolution record; fixed broken cross-references (§2.2, §3.4, §4.1). Per maintainer review of the consolidation: thecoalescingprovenance object gains REQUIREDjunction_angleandmax_level_rowsmembers so coalescing is reproducible from the file alone (§13.4);ranking.ranksis specified as a JSON object map, not an array of pairs (§3.5); and the §12.1 cluster accounting /Σ point_countsum invariant is strict-normative at every level, including under density-budget drops. Backward compatible: v0.1.0 files remain conforming, and extension files remain readable by baseline readers (§3.8). - v0.1.0 (2026-07-02): initial draft; §11 design decisions approved.