Project
Portolan STAC Profile¶
Work in Progress — This profile is under active development. Requirement levels and the schema URL may change before the first stable release.
- Title: Portolan
- Identifier: https://schemas.portolan-sdi.org/portolan/v0.2.0/schema.json
- Field Name Prefix: portolan (reserved; no fields defined yet)
- Scope: Catalog, Collection — Items inherit conformance from their collection
- Extension Maturity Classification: Proposal
A Portolan catalog is a directory of STAC metadata and cloud-native geospatial data, served from plain object storage and consumable with zero infrastructure. Following the approach of the CEOS-ARD extension, this directory is both a minimal extension — a JSON Schema for the structural requirements checkable from STAC JSON alone — and a profile: how the Portolan specification uses STAC and existing extensions, which link relations, media types, and roles it requires, and where full validation happens.
Declaring this extension is a claim of conformance, not proof of it: an object conforms only by passing the Portolan validator (see Validation).
Fields¶
This extension defines no fields. The versioned schema URI declared in stac_extensions is the single signal of specification version; no separate version property exists (spec: Conformance and Versioning). Dataset versioning uses the version extension, never portolan: fields.
Declaration happens at the catalog and collection level only — every catalog and collection MUST declare the schema URI in stac_extensions; items inherit conformance from their collection and do not declare it. All objects within a catalog SHOULD declare the same URI; a validator flags a mismatch with the root catalog as a warning, not an error — a mixed-version catalog remains valid.
The portolan: prefix stays reserved for future use.
What the schema enforces¶
The schema encodes the specification's structural requirements that STAC core leaves optional:
- Every Catalog and Collection has a non-empty
titleanddescription; everychildanditemlink carries atitle(spec: Human-Readable Titles). - Structural links (
root,parent,child,item,collection) carrytype: "application/json"(application/geo+jsonfor links to items) (spec: Links). The schema takes no position on relative versus absolute hrefs, and it accepts aselflink. The schemas through v0.1.2 reject aselflink and require a relative structural href. v0.2.0 drops both rules, following the specification. - Every asset has
href, a mediatype, and at least onerole(spec: Assets).file:sizeandfile:checksumare SHOULD-level, so the schema checks their shape where present without requiring them; the checksum's multihash encoding, and whether either value matches the bytes, are verified by tooling rather than schema. - Absolute asset hrefs use
https—s3://and other bucket schemes are rejected; relative hrefs are allowed (spec: Assets). - Every Collection declares
providerswith at least oneproducerand ahostreachable throughurloremail; that the host is exactly one and listed last is checked by tooling (spec: Providers). - Collection
licenseis never the deprecatedproprietary; SPDX validity and therel: "license"link required withotherare checked by tooling (spec: License). - Every
bbox— collection extent and item — contains only in-range WGS84 coordinates, with no sentinel "effectively infinite" values (spec: Bounding Boxes and Spatial Extent). - Every Catalog and Collection links its
AGENTS.mdwithrel: "agents"andtype: "text/markdown"(spec: AGENTS.md). - Every Catalog and Collection links its
README.mdwithrel: "describedby"andtype: "text/markdown"(spec: README.md).
STAC Extensions¶
Portolan reuses established extensions rather than re-encoding the same information under portolan:. This table is the normative registry of the extensions the profile uses — the requirement level, the condition under which it applies, and the exact schema URI to pin in stac_extensions. The specification defers to this table rather than restating it.
Requirement keywords per BCP 14; a conditional MUST applies only when its condition holds.
| Name | Schema URI for stac_extensions |
Requirement | When / Usage |
|---|---|---|---|
| Portolan | https://schemas.portolan-sdi.org/portolan/v0.2.0/schema.json |
MUST | Always — every catalog and collection; items inherit conformance |
| File Info | https://stac-extensions.github.io/file/v2.1.0/schema.json |
MUST | When an asset carries file:size or file:checksum, both of which are SHOULD-level per asset (checksum as multihash) |
| Web Map Links | https://stac-extensions.github.io/web-map-links/v1.3.0/schema.json |
MUST | When PMTiles are provided: the rel: "pmtiles" link |
| Version | https://stac-extensions.github.io/version/v1.2.0/schema.json |
MUST | When dataset versioning is used (never portolan: fields) |
| Raster | https://stac-extensions.github.io/raster/v2.0.0/schema.json |
MUST | When band-level detail is provided |
| Vector | https://stac-extensions.github.io/vector/v0.1.0/schema.json |
MUST | When layer-level detail is provided |
| Partition | https://schemas.portolan-sdi.org/incubating/partition/v1.0.0/schema.json |
MUST | Partitioned collections: partition:scheme, partition:keys, partition:glob (spec: Partitioned Collections) |
| Table | https://stac-extensions.github.io/table/v1.2.0/schema.json |
SHOULD | Vector and tabular collections: document columns with table:columns |
| Alternate Assets | https://stac-extensions.github.io/alternate-assets/v1.2.0/schema.json |
SHOULD | Expose s3:// alternates for absolute https asset hrefs |
| Render | https://stac-extensions.github.io/render/v2.0.0/schema.json |
SHOULD | Continuous rasters rendering from source (draw-time colorization) |
| Projection | https://stac-extensions.github.io/projection/v2.0.0/schema.json |
MAY | CRS / projection of the data |
| Scientific | https://stac-extensions.github.io/scientific/v1.0.0/schema.json |
MAY | Citation / DOI |
| Contacts | https://stac-extensions.github.io/contacts/v0.1.1/schema.json |
MAY | Richer contact info; never replaces url-or-email on the host provider |
| Attribution | https://stac-extensions.github.io/attribution/v0.1.0/schema.json |
MAY | Display attribution |
| Themes | https://stac-extensions.github.io/themes/v1.0.0/schema.json |
MAY | Thematic classification |
Note that item_assets needs no extension — it is a core field in STAC 1.1.
As the profile grows, per-format requirement sets (vector, raster, tabular) may split into separate subprofile documents, as CEOS-ARD does for optical and SAR.
Profile¶
Link relations¶
Structural links can be relative or absolute; the specification takes no position, and the STAC best practices on the use of links cover the trade-offs. A catalog served over the internet from a single fixed URL SHOULD carry an absolute self link on its root catalog (spec: Links).
rel |
Where | Notes |
|---|---|---|
root, parent |
All objects (root catalog has no parent) |
type: "application/json" |
child / item |
Catalogs and collections, one per child | Typed (application/geo+json for items); MUST carry a title |
collection |
Items | type: "application/json" |
self |
Root catalog | Absolute; SHOULD when the catalog is served from a single fixed URL |
agents |
Catalog, Collection | Points to AGENTS.md, type: "text/markdown" |
describedby |
Catalog, Collection | Points to README.md, type: "text/markdown" |
via |
Collection | Mirror only: the original source, type: "text/html" |
canonical |
Collection | Mirror only: the source's own STAC root, MUST when the source publishes STAC |
license |
Collection | License text, MUST when license is other |
pmtiles |
Collection | Per web-map-links v1.3.0, with pmtiles:layers, when PMTiles are provided |
Whether a catalog is official or a mirror is derived from its providers (producer = host means official); a mirror records each sync in the core updated field. Every link in a catalog MUST resolve — a link that 404s is a conformance failure. This is a crawling check, outside JSON Schema.
Media types and roles¶
| Format | Media type | Typical role |
|---|---|---|
| GeoParquet / Parquet | application/vnd.apache.parquet |
data |
| COG | image/tiff; application=geotiff; profile=cloud-optimized |
data |
| PMTiles | application/vnd.pmtiles |
visual |
| COPC | application/vnd.laszip+copc |
data |
| Thumbnail | image/png, image/jpeg, or image/webp |
thumbnail |
| Sidecar metadata | (format-specific) | metadata, iso-19115 |
| MapLibre style | application/vnd.mapbox.style+json |
style (Portolan-defined role) |
| STAC-GeoParquet item mirror | application/vnd.apache.parquet |
collection-mirror (stac-geoparquet role) |
| Upstream original | (format-specific) | source (Portolan-defined role) |
Styles are STAC assets: each style file is a collection-level asset with roles: ["style"], discovered by filtering assets on that role — no separate manifest exists (spec: Visualization Styles).
A source asset names the upstream original a cloud-native asset was derived from, so the format requirements do not reach it. A mirror should carry one when the original is a direct download, which means referencing it at the upstream href, not rehosting the file. collection-mirror comes from the stac-geoparquet specification, not from Portolan, and is required on a published item mirror. Neither role is enumerated in the JSON Schema, which accepts any non-empty role string.
Formats¶
Every collection and item is available in a cloud-optimized format; full requirements live in the specification's format sections, and the data-file internals are validated by tooling, not schema:
- Vector — GeoParquet 1.1/2.0 (compression, spatial ordering, row-group statistics); a PMTiles visualization derivative SHOULD be provided, registered through the
rel: "pmtiles"link (an asset only when also intended for distribution), with MapLibre styles asstyleassets. - Raster — COG with embedded per-band GDAL statistics in the leading header block.
- Tabular (non-geospatial) — Parquet as a collection-level asset, columns documented with the table extension, spatial requirements relaxed. No marker property: a tabular collection is identified by its data, a Parquet asset with no geometry column.
- Point cloud — reserved; COPC, pending a reference implementation.
Validation¶
Per the specification, validation runs in separable passes:
- Structural — STAC 1.1.0 core schemas.
- Metadata — every requirement checkable from metadata alone. This profile's schema is the machine-checkable core of this pass; link resolution needs a crawler.
- Data — requirements that need asset bytes (GeoParquet spatial ordering, row-group statistics, embedded COG statistics). Run by
portolan check, MAY run independently.
Hosting requirements (HTTP Range support, CORS on all metadata and asset files) are properties of the server, validated by probe.
Open questions¶
Tracked in the specification and deliberately not settled by this schema:
- Raster styling — how raster styles are expressed (colormaps, legends, continuous vs. categorical vs. multiband) is under discussion in
specs/incubating/raster-styling.md; the MapLibre style requirements are vector-only for now.
Examples¶
- Root catalog
- Single-file vector collection — GeoParquet + PMTiles + style + thumbnail as collection-level assets
- Partitioned vector collection and partition item
The files are stored flat in examples/ for convenience, but their relative hrefs describe the specification's canonical directory layout ({collection_id}/collection.json, {item_id}/item.json beneath it) — so href targets such as ../catalog.json, AGENTS.md, and the data files are illustrative and not present in this repository. The examples carry no self links and use relative structural hrefs, the portable layout the git-backed best practices recommend for a tracked tree.
Building and Testing¶
Every published schema version is tracked in this repository under json-schema/v<version>/schema.json; the version field in package.json names the current one and is the single source of truth. The portolan-sdi extension schemas (currently partition) live in their own repositories; portolan-extensions.json pins which versions are published under schemas.portolan-sdi.org/<name>/<version>/, so bumping a pin — like binding the profile to a new extension version — is a change to this repository. The publish workflow deploys the site from the tracked tree plus the pinned extension schemas fetched from their source repos.
From stac/:
npm install
npm test
npm test runs (after a pretest step that fetches the pinned extension schemas into the gitignored .schema-cache/):
- check-markdown — remark lint over the profile documents.
- check-version — every versioned Portolan schema URI reference (schema
$id, READMEs, examples, spec documents) matches thepackage.jsonversion, and the matchingjson-schema/v<version>/directory exists. - check-examples — stac-node-validator validates the examples, applying the schema where
stac_extensionsdeclares it. - check-portolan — validates every example directly against the schema with ajv, the way the Portolan validator does. This is what exercises the schema's item rules — items never declare the schema URI, so
check-examplesalone would never apply it to them. It also applies the pinned extension schemas to every example that declares them, and verifies the examples'file:checksumvalues are well-formed sha2-256 multihashes, which the schema deliberately delegates to tooling.
Contributing¶
This profile is maintained by the Portolan SDI project. Issues and pull requests are welcome.
License¶
Apache-2.0