From 11093d63ffca2d9747105ff3267c2d06977d8e65 Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Thu, 16 Jul 2026 11:35:41 +0200 Subject: [PATCH 1/2] STAC well known types for Assets, Links, Storage, Authentication and Providers --- apis/datasets/stac/v1/asset.proto | 186 ++++++++++++++++++++ apis/datasets/stac/v1/asset_metadata.proto | 124 +++++++++++++ apis/datasets/stac/v1/authentication.proto | 191 +++++++++++++++++++++ apis/datasets/stac/v1/core.proto | 133 ++++++++++++++ apis/datasets/stac/v1/processing.proto | 15 ++ apis/datasets/stac/v1/product.proto | 42 +++++ apis/datasets/stac/v1/sar.proto | 99 +++++++++++ apis/datasets/stac/v1/satellite.proto | 54 ++++++ apis/datasets/stac/v1/storage.proto | 61 +++++++ 9 files changed, 905 insertions(+) create mode 100644 apis/datasets/stac/v1/asset.proto create mode 100644 apis/datasets/stac/v1/asset_metadata.proto create mode 100644 apis/datasets/stac/v1/authentication.proto create mode 100644 apis/datasets/stac/v1/core.proto create mode 100644 apis/datasets/stac/v1/processing.proto create mode 100644 apis/datasets/stac/v1/product.proto create mode 100644 apis/datasets/stac/v1/sar.proto create mode 100644 apis/datasets/stac/v1/satellite.proto create mode 100644 apis/datasets/stac/v1/storage.proto diff --git a/apis/datasets/stac/v1/asset.proto b/apis/datasets/stac/v1/asset.proto new file mode 100644 index 0000000..bf7a008 --- /dev/null +++ b/apis/datasets/stac/v1/asset.proto @@ -0,0 +1,186 @@ +// This file models STAC 1.1 Asset and Band Objects using Tilebox's compact shared-profile representation. Access and band +// profile tables deduplicate repeated metadata without changing the reconstructed STAC semantics. +// +// https://github.com/radiantearth/stac-spec/blob/v1.1.0/item-spec/item-spec.md#asset-object + +edition = "2023"; +package datasets.stac.v1; + +import "buf/validate/validate.proto"; +import "datasets/stac/v1/asset_metadata.proto"; +import "datasets/stac/v1/core.proto"; +import "datasets/stac/v1/product.proto"; +import "datasets/stac/v1/sar.proto"; +import "datasets/stac/v1/satellite.proto"; + +option features.field_presence = IMPLICIT; + +// KnownAssetRole contains the STAC 1.1, EO 2.0, and SAR 1.3.2 recommended asset roles. STAC asset roles are open; +// values not listed here are stored in Asset.custom_roles. +enum KnownAssetRole { + KNOWN_ASSET_ROLE_UNSPECIFIED = 0; + + // STAC 1.1 recommended asset roles occupy values 1-99. + // https://github.com/radiantearth/stac-spec/blob/v1.1.0/best-practices.md#list-of-asset-roles + KNOWN_ASSET_ROLE_DATA = 1; + KNOWN_ASSET_ROLE_METADATA = 2; + KNOWN_ASSET_ROLE_THUMBNAIL = 3; + KNOWN_ASSET_ROLE_OVERVIEW = 4; + KNOWN_ASSET_ROLE_VISUAL = 5; + KNOWN_ASSET_ROLE_DATE = 6; + KNOWN_ASSET_ROLE_GRAPHIC = 7; + KNOWN_ASSET_ROLE_DATA_MASK = 8; + KNOWN_ASSET_ROLE_SNOW_ICE = 9; + KNOWN_ASSET_ROLE_LAND_WATER = 10; + KNOWN_ASSET_ROLE_WATER_MASK = 11; + KNOWN_ASSET_ROLE_ISO_19115 = 12; + + // EO 2.0 best-practice asset roles occupy values 100-199. + // https://github.com/stac-extensions/eo/blob/v2.0.0/README.md#best-practices + KNOWN_ASSET_ROLE_REFLECTANCE = 100; + KNOWN_ASSET_ROLE_TEMPERATURE = 101; + KNOWN_ASSET_ROLE_SATURATION = 102; + KNOWN_ASSET_ROLE_CLOUD = 103; + KNOWN_ASSET_ROLE_CLOUD_SHADOW = 104; + + // SAR 1.3.2 best-practice asset roles occupy values 200-299. + // https://github.com/stac-extensions/sar/blob/v1.3.2/README.md#best-practices + KNOWN_ASSET_ROLE_LOCAL_INCIDENCE_ANGLE = 200; + KNOWN_ASSET_ROLE_ELLIPSOID_INCIDENCE_ANGLE = 201; + KNOWN_ASSET_ROLE_NOISE_POWER = 202; + KNOWN_ASSET_ROLE_AMPLITUDE = 203; + KNOWN_ASSET_ROLE_MAGNITUDE = 204; + KNOWN_ASSET_ROLE_SIGMA0 = 205; + KNOWN_ASSET_ROLE_BETA0 = 206; + KNOWN_ASSET_ROLE_GAMMA0 = 207; + KNOWN_ASSET_ROLE_DATE_OFFSET = 208; + KNOWN_ASSET_ROLE_COVMAT = 209; + KNOWN_ASSET_ROLE_PRD = 210; +} + +// DataType contains the complete closed STAC 1.1 data_type vocabulary. +enum DataType { + DATA_TYPE_UNSPECIFIED = 0; + DATA_TYPE_INT8 = 1; + DATA_TYPE_INT16 = 2; + DATA_TYPE_INT32 = 3; + DATA_TYPE_INT64 = 4; + DATA_TYPE_UINT8 = 5; + DATA_TYPE_UINT16 = 6; + DATA_TYPE_UINT32 = 7; + DATA_TYPE_UINT64 = 8; + DATA_TYPE_FLOAT16 = 9; + DATA_TYPE_FLOAT32 = 10; + DATA_TYPE_FLOAT64 = 11; + DATA_TYPE_CINT16 = 12; + DATA_TYPE_CINT32 = 13; + DATA_TYPE_CFLOAT32 = 14; + DATA_TYPE_CFLOAT64 = 15; + DATA_TYPE_OTHER = 16; +} + +// Statistics contains the statistics fields used by the supported Items. +message Statistics { + double minimum = 1 [features.field_presence = EXPLICIT]; + double maximum = 2 [features.field_presence = EXPLICIT]; +} + +// AssetAccessProfile deduplicates access metadata shared by asset locations in one STAC Item. +message AssetAccessProfile { + // Alternate Assets map key, such as `s3` or `https`, when used for an alternate location. + string alternate_key = 1; + // Default alternate:name for locations using this profile. + string default_alternate_name = 2 [features.field_presence = EXPLICIT]; + // Item-specific common URI prefix used to compress hrefs. A location's full href is the byte concatenation of + // base_href and its href suffix; URI reference resolution and path normalization are not applied. + string base_href = 3; + // Keys into the item-level Storage.schemes registry. + repeated string storage_refs = 4; + // Keys into the item-level Authentication.schemes registry. + repeated string auth_refs = 5; +} + +// AssetLocation selects an access profile and supplies a relative or absolute href. +message AssetLocation { + // Required index into Assets.access_profiles. Zero is a valid index. + uint32 access_profile_index = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).required = true + ]; + // Href suffix for the selected profile. This must be present on primary locations. When absent on an alternate, + // it reuses the primary location's href suffix verbatim before applying the alternate profile's base_href. + // A present empty string represents the selected profile's base_href exactly. + string href = 2 [features.field_presence = EXPLICIT]; + // Per-location override for the access profile's default alternate:name. + string alternate_name = 3 [features.field_presence = EXPLICIT]; +} + +// Band is a reusable STAC 1.1 band profile containing common, EO, Raster, and Classification fields. Fields present +// here override the corresponding inheritable Asset fields; ingestion should omit values lifted to the Asset. +message Band { + string name = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + string description = 2 [features.field_presence = EXPLICIT]; + DataType data_type = 3 [(buf.validate.field).enum.defined_only = true]; + double nodata = 4 [features.field_presence = EXPLICIT]; + string unit = 5 [features.field_presence = EXPLICIT]; + EOProperties eo = 6; + RasterProperties raster = 7; + repeated ClassificationClass classes = 8; + // SAR 1.3.2 permits frequency, center-frequency, and bandwidth metadata on Band Objects. + SARProperties sar = 9; +} + +// Asset contains the canonical STAC 1.1 Asset Object fields used by the supported Items. Inheritable metadata applies +// to every band unless a Band overrides it. Ingestion should lift values shared by all bands to this level. +message Asset { + option (buf.validate.message).cel = { + id: "asset.primary_href" + message: "the primary asset location must provide an href" + expression: "has(this.primary.href)" + }; + + // Key from the STAC assets object. + string key = 1 [(buf.validate.field).string.min_len = 1]; + AssetLocation primary = 2 [(buf.validate.field).required = true]; + repeated AssetLocation alternates = 3; + MediaType media_type = 4; + string title = 5 [features.field_presence = EXPLICIT]; + string description = 6 [features.field_presence = EXPLICIT]; + // STAC role order is not semantically meaningful. Known roles are stored compactly; all other values remain strings. + repeated KnownAssetRole roles = 7 [(buf.validate.field).repeated.items.enum = { + defined_only: true + not_in: [0] + }]; + repeated string custom_roles = 8 [(buf.validate.field).repeated.items.string.min_len = 1]; + double gsd = 9 [features.field_presence = EXPLICIT]; + // Ordered indices into Assets.band_profiles. References deliberately have no per-use overrides: metadata differences + // require distinct profiles, which keeps reconstruction to direct lookup plus normal STAC Asset-to-Band inheritance. + repeated uint32 band_profile_indices = 10; + DataType data_type = 11 [(buf.validate.field).enum.defined_only = true]; + double nodata = 12 [features.field_presence = EXPLICIT]; + Statistics statistics = 13; + string unit = 14 [features.field_presence = EXPLICIT]; + EOProperties eo = 15; + RasterProperties raster = 16; + Projection projection = 17; + View view = 18; + repeated ClassificationClass classes = 19; + File file = 20; + SARProperties sar = 21; + SatelliteProperties satellite = 22; + ProductProperties product = 23; +} + +// Assets contains the shared profile tables and assets of one STAC Item. Asset locations reference access_profiles by +// index, and assets reference band_profiles by ordered indices. Both tables deduplicate repeated metadata; every index +// must reference an existing profile. Band references intentionally cannot override profiles: use a distinct profile +// for every unique Band. Use one access profile with an empty base_href and refs for uncompressed absolute hrefs. +// Empty repeated fields are intentionally canonicalized: protobuf does not distinguish an absent JSON array from []. +message Assets { + repeated AssetAccessProfile access_profiles = 1; + repeated Band band_profiles = 2; + repeated Asset assets = 3; +} diff --git a/apis/datasets/stac/v1/asset_metadata.proto b/apis/datasets/stac/v1/asset_metadata.proto new file mode 100644 index 0000000..6989598 --- /dev/null +++ b/apis/datasets/stac/v1/asset_metadata.proto @@ -0,0 +1,124 @@ +// This file models the common STAC extensions used to describe imagery assets, bands, pixel values, spatial grids, +// viewing geometry, and files in Tilebox datasets. +// EO 2.0: https://github.com/stac-extensions/eo/blob/v2.0.0/README.md +// Raster 2.0: https://github.com/stac-extensions/raster/blob/v2.0.0/README.md +// Classification 2.0: https://github.com/stac-extensions/classification/blob/v2.0.0/README.md +// Projection 2.0: https://github.com/stac-extensions/projection/blob/v2.0.0/README.md +// View 1.1: https://github.com/stac-extensions/view/blob/v1.1.0/README.md +// File 2.1: https://github.com/stac-extensions/file/blob/v2.1.0/README.md + +edition = "2023"; +package datasets.stac.v1; + +import "buf/validate/validate.proto"; + +option features.field_presence = IMPLICIT; + +// EOCommonName is the closed EO 2.0 common-name vocabulary. +enum EOCommonName { + EO_COMMON_NAME_UNSPECIFIED = 0; + EO_COMMON_NAME_PAN = 1; + EO_COMMON_NAME_COASTAL = 2; + EO_COMMON_NAME_BLUE = 3; + EO_COMMON_NAME_GREEN = 4; + EO_COMMON_NAME_GREEN05 = 5; + EO_COMMON_NAME_YELLOW = 6; + EO_COMMON_NAME_RED = 7; + EO_COMMON_NAME_REDEDGE = 8; + EO_COMMON_NAME_REDEDGE071 = 9; + EO_COMMON_NAME_REDEDGE075 = 10; + EO_COMMON_NAME_REDEDGE078 = 11; + EO_COMMON_NAME_NIR = 12; + EO_COMMON_NAME_NIR08 = 13; + EO_COMMON_NAME_NIR09 = 14; + EO_COMMON_NAME_CIRRUS = 15; + EO_COMMON_NAME_SWIR16 = 16; + EO_COMMON_NAME_SWIR22 = 17; + EO_COMMON_NAME_LWIR = 18; + EO_COMMON_NAME_LWIR11 = 19; + EO_COMMON_NAME_LWIR12 = 20; +} + +// EOProperties contains the EO 2.0 fields that are useful at asset or band level. +// eo:cloud_cover and eo:snow_cover are intentionally omitted: they are typically Item properties and should become +// dedicated top-level Tilebox dataset fields instead of being embedded in Assets. +message EOProperties { + EOCommonName common_name = 1 [(buf.validate.field).enum.defined_only = true]; + // Spectral values are expressed in micrometres. + double center_wavelength = 2 [features.field_presence = EXPLICIT]; + double full_width_half_max = 3 [features.field_presence = EXPLICIT]; + double solar_illumination = 4 [features.field_presence = EXPLICIT]; +} + +// RasterSampling contains the closed Raster 2.0 raster:sampling vocabulary. +enum RasterSampling { + RASTER_SAMPLING_UNSPECIFIED = 0; + RASTER_SAMPLING_AREA = 1; + RASTER_SAMPLING_POINT = 2; +} + +// RasterProperties contains the Raster 2.0 fields used by the supported Items. +message RasterProperties { + RasterSampling sampling = 1 [(buf.validate.field).enum.defined_only = true]; + double scale = 2 [features.field_presence = EXPLICIT]; + double offset = 3 [features.field_presence = EXPLICIT]; + // Spatial resolution is expressed in metres. + double spatial_resolution = 4 [features.field_presence = EXPLICIT]; +} + +// ClassificationClass contains all Classification 2.0 class fields. Bit-field definitions are intentionally deferred. +message ClassificationClass { + int64 value = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).required = true + ]; + string description = 2 [features.field_presence = EXPLICIT]; + string name = 3 [ + features.field_presence = EXPLICIT, + (buf.validate.field).required = true, + (buf.validate.field).string.pattern = "^[0-9A-Za-z-_]+$" + ]; + string title = 4 [features.field_presence = EXPLICIT]; + string color_hint = 5 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.pattern = "^[0-9A-Fa-f]{6}$" + ]; + bool nodata = 6 [features.field_presence = EXPLICIT]; + double percentage = 7 [ + features.field_presence = EXPLICIT, + (buf.validate.field).double = { + gte: 0 + lte: 100 + } + ]; + uint64 count = 8 [features.field_presence = EXPLICIT]; +} + +// Projection contains the Projection 2.0 fields used by the supported Items. +message Projection { + // Explicit presence distinguishes a missing code from a value. A STAC JSON null is treated as missing. + string code = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + // Four values describe a 2D bbox and six values describe a 3D bbox. + repeated double bbox = 2; + // Exactly two integers in Y, X order. + repeated int64 shape = 3; + // Six values describe a 2D affine transform and nine values describe a 3D transform. + repeated double transform = 4; +} + +// View contains the View 1.1 fields used by the supported Items. +message View { + double incidence_angle = 1 [features.field_presence = EXPLICIT]; + double azimuth = 2 [features.field_presence = EXPLICIT]; +} + +// File contains the File 2.1 fields used by the supported Items. +message File { + // Decoded binary multihash. STAC JSON represents this as lowercase hexadecimal. + bytes checksum = 1 [features.field_presence = EXPLICIT]; + uint64 size = 2 [features.field_presence = EXPLICIT]; + string local_path = 3 [features.field_presence = EXPLICIT]; +} diff --git a/apis/datasets/stac/v1/authentication.proto b/apis/datasets/stac/v1/authentication.proto new file mode 100644 index 0000000..f9c5001 --- /dev/null +++ b/apis/datasets/stac/v1/authentication.proto @@ -0,0 +1,191 @@ +// This file models the STAC Authentication 1.1 extension. Authentication schemes describe how access is granted; +// concrete hrefs remain on assets and links, while their auth_refs select registry entries. +// https://github.com/stac-extensions/authentication/blob/v1.1.0/README.md + +edition = "2023"; +package datasets.stac.v1; + +import "buf/validate/validate.proto"; +import "google/protobuf/struct.proto"; + +option features.field_presence = IMPLICIT; + +// Authentication stores the Authentication 1.1 auth:schemes registry. +message Authentication { + map schemes = 1; +} + +// KnownAuthenticationType is Tilebox's compact dictionary for the standard Authentication 1.1 scheme types, in the +// same order as the extension specification. Authentication 1.1 has an open type vocabulary; all other values remain +// available through AuthenticationScheme.custom_type. +enum KnownAuthenticationType { + KNOWN_AUTHENTICATION_TYPE_UNSPECIFIED = 0; + // http + KNOWN_AUTHENTICATION_TYPE_HTTP = 1; + // s3 + KNOWN_AUTHENTICATION_TYPE_S3 = 2; + // signedUrl + KNOWN_AUTHENTICATION_TYPE_SIGNED_URL = 3; + // oauth2 + KNOWN_AUTHENTICATION_TYPE_OAUTH2 = 4; + // apiKey + KNOWN_AUTHENTICATION_TYPE_API_KEY = 5; + // openIdConnect + KNOWN_AUTHENTICATION_TYPE_OPEN_ID_CONNECT = 6; +} + +// AuthenticationScheme describes one reusable authentication mechanism. +message AuthenticationScheme { + option (buf.validate.message).oneof = { + fields: [ + "known_type", + "custom_type" + ] + required: true + }; + option (buf.validate.message).cel = { + id: "authentication_scheme.api_key_fields" + message: "only apiKey authentication requires and permits name and location" + expression: "has(this.known_type) && this.known_type == 5 ? has(this.name) && has(this.location) : !has(this.name) && !has(this.location)" + }; + option (buf.validate.message).cel = { + id: "authentication_scheme.http_fields" + message: "only http authentication requires and permits scheme" + expression: "has(this.known_type) && this.known_type == 1 ? has(this.scheme) : !has(this.scheme)" + }; + option (buf.validate.message).cel = { + id: "authentication_scheme.flows" + message: "oauth2 authentication requires OAuth2 flows, signedUrl authentication requires signed URL flows, and other types do not permit flows" + expression: "has(this.known_type) && this.known_type == 4 ? this.flows.size() > 0 && this.flows.all(flow, has(flow.oauth2)) : has(this.known_type) && this.known_type == 3 ? this.flows.size() > 0 && this.flows.all(flow, has(flow.signed_url)) : this.flows.size() == 0" + }; + option (buf.validate.message).cel = { + id: "authentication_scheme.unique_flow_keys" + message: "authentication flow keys must be unique within a scheme" + expression: "this.flows.all(flow, this.flows.filter(other, other.key == flow.key).size() == 1)" + }; + option (buf.validate.message).cel = { + id: "authentication_scheme.open_id_connect_fields" + message: "only openIdConnect authentication requires and permits open_id_connect_url" + expression: "has(this.known_type) && this.known_type == 6 ? has(this.open_id_connect_url) : !has(this.open_id_connect_url)" + }; + + // Exactly one compact known type or exact custom Authentication 1.1 type string is set. + KnownAuthenticationType known_type = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).enum = { + defined_only: true + not_in: [0] + } + ]; + string custom_type = 2 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + string description = 3 [features.field_presence = EXPLICIT]; + // API key parameter name. Required for apiKey authentication. + string name = 4 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + // API key parameter location, conventionally `query`, `header`, or `cookie`. The vocabulary is open in the extension. + string location = 5 [ + json_name = "in", + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + // HTTP Authorization scheme, such as `basic` or `bearer`. The vocabulary is open in the extension. + string scheme = 6 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + // OAuth2 or signed URL flows. Each key corresponds to one entry in the STAC flows map. + repeated AuthenticationFlow flows = 7; + // OpenID Connect discovery URL. Required when known_type is OPEN_ID_CONNECT. + string open_id_connect_url = 8 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.uri = true + ]; +} + +// AuthenticationFlow models one keyed entry in an Authentication Scheme Object's flows map. Exactly one flow value is +// set without using a protobuf oneof, keeping generated client APIs straightforward. +message AuthenticationFlow { + option (buf.validate.message).oneof = { + fields: [ + "oauth2", + "signed_url" + ] + required: true + }; + option (buf.validate.message).cel = { + id: "authentication_flow.authorization_url" + message: "implicit and authorizationCode OAuth2 flows require authorization_url" + expression: "!has(this.oauth2) || !(this.key == 'implicit' || this.key == 'authorizationCode') || has(this.oauth2.authorization_url)" + }; + option (buf.validate.message).cel = { + id: "authentication_flow.token_url" + message: "password, clientCredentials, and authorizationCode OAuth2 flows require token_url" + expression: "!has(this.oauth2) || !(this.key == 'password' || this.key == 'clientCredentials' || this.key == 'authorizationCode') || has(this.oauth2.token_url)" + }; + + // Flow map key. OAuth2 defines authorizationCode, implicit, password, and clientCredentials; custom keys are allowed. + string key = 1 [(buf.validate.field).string.min_len = 1]; + OAuth2Flow oauth2 = 2; + SignedURLFlow signed_url = 3; +} + +// OAuth2Flow models an Authentication 1.1 OAuth2 Flow Object. +message OAuth2Flow { + string authorization_url = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.uri = true + ]; + string token_url = 2 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.uri = true + ]; + // STAC requires scopes, but permits an empty map. Always emit an empty object when no scopes are available. + map scopes = 3; + string refresh_url = 4 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.uri = true + ]; +} + +// SignedURLFlow models an Authentication 1.1 Signed URL Object. +message SignedURLFlow { + string method = 1 [(buf.validate.field).string.min_len = 1]; + string authorization_api = 2 [(buf.validate.field).string.uri = true]; + map parameters = 3; + string response_field = 4 [features.field_presence = EXPLICIT]; +} + +// AuthenticationParameter models a request parameter for a signed URL authorization API. +message AuthenticationParameter { + // Parameter location, conventionally `query`, `header`, or `body`. The vocabulary is open in the extension. + string location = 1 [ + json_name = "in", + (buf.validate.field).string.min_len = 1 + ]; + bool required = 2 [ + features.field_presence = EXPLICIT, + (buf.validate.field).required = true + ]; + string description = 3 [features.field_presence = EXPLICIT]; + JSONSchema schema = 4; +} + +// JSONSchema represents the object and boolean forms allowed by JSON Schema draft-07. The object form is intentionally +// kept as Struct: Authentication parameters may use any draft-07 keywords, including nested schemas and custom keywords. +message JSONSchema { + option (buf.validate.message).oneof = { + fields: [ + "object", + "boolean" + ] + required: true + }; + + google.protobuf.Struct object = 1; + bool boolean = 2 [features.field_presence = EXPLICIT]; +} diff --git a/apis/datasets/stac/v1/core.proto b/apis/datasets/stac/v1/core.proto new file mode 100644 index 0000000..e43ddc5 --- /dev/null +++ b/apis/datasets/stac/v1/core.proto @@ -0,0 +1,133 @@ +// This file contains compact representations of the core STAC 1.1 metadata shared by assets, links, +// and properties (which are modelled as direct Tilebox dataset fields). + +// https://github.com/radiantearth/stac-spec/tree/v1.1.0 + +edition = "2023"; +package datasets.stac.v1; + +import "buf/validate/validate.proto"; + +option features.field_presence = IMPLICIT; + +// KnownMediaType is a compact dictionary of media types commonly used by STAC assets and links. +enum KnownMediaType { + KNOWN_MEDIA_TYPE_UNSPECIFIED = 0; + // application/geo+json + KNOWN_MEDIA_TYPE_GEOJSON = 1; + // application/json + KNOWN_MEDIA_TYPE_JSON = 2; + // image/tiff; application=geotiff; profile=cloud-optimized + KNOWN_MEDIA_TYPE_CLOUD_OPTIMIZED_GEOTIFF = 3; + // image/jp2 + KNOWN_MEDIA_TYPE_JPEG_2000 = 4; + // image/jpeg + KNOWN_MEDIA_TYPE_JPEG = 5; + // image/png + KNOWN_MEDIA_TYPE_PNG = 6; + // application/xml + KNOWN_MEDIA_TYPE_APPLICATION_XML = 7; + // application/zip + KNOWN_MEDIA_TYPE_ZIP = 8; + // application/x-directory + KNOWN_MEDIA_TYPE_DIRECTORY = 9; + // image/tiff; application=geotiff + KNOWN_MEDIA_TYPE_GEOTIFF = 10; + // image/tiff + KNOWN_MEDIA_TYPE_TIFF = 11; + // application/x-hdf5 + KNOWN_MEDIA_TYPE_HDF5 = 12; + // application/x-hdf + KNOWN_MEDIA_TYPE_HDF = 13; + // application/netcdf + KNOWN_MEDIA_TYPE_NETCDF = 14; + // application/vnd.zarr; version=2 + KNOWN_MEDIA_TYPE_ZARR_V2 = 15; + // application/vnd.zarr; version=3 + KNOWN_MEDIA_TYPE_ZARR_V3 = 16; + // application/vnd.apache.parquet + KNOWN_MEDIA_TYPE_PARQUET = 17; + // application/geopackage+sqlite3 + KNOWN_MEDIA_TYPE_GEOPACKAGE = 18; + // application/vnd.laszip+copc + KNOWN_MEDIA_TYPE_COPC = 19; + // text/html + KNOWN_MEDIA_TYPE_HTML = 20; + // text/plain + KNOWN_MEDIA_TYPE_TEXT = 21; + // text/xml + KNOWN_MEDIA_TYPE_TEXT_XML = 22; + // application/vnd.flatgeobuf + KNOWN_MEDIA_TYPE_FLATGEOBUF = 23; + // application/vnd.pmtiles + KNOWN_MEDIA_TYPE_PMTILES = 24; + // application/vnd.nitf + KNOWN_MEDIA_TYPE_NITF = 25; + // application/octet-stream + KNOWN_MEDIA_TYPE_OCTET_STREAM = 26; +} + +// MediaType stores a common media type compactly while retaining STAC's open media-type vocabulary. +message MediaType { + option (buf.validate.message).oneof = { + fields: [ + "known", + "custom" + ] + required: true + }; + + // The exact media type represented by either the compact dictionary or a custom string. Exactly one is set. + KnownMediaType known = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).enum = { + defined_only: true + not_in: [0] + } + ]; + string custom = 2 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; +} + +// ProviderRole contains the closed STAC 1.1 Provider role vocabulary. +enum ProviderRole { + PROVIDER_ROLE_UNSPECIFIED = 0; + PROVIDER_ROLE_PRODUCER = 1; + PROVIDER_ROLE_LICENSOR = 2; + PROVIDER_ROLE_PROCESSOR = 3; + PROVIDER_ROLE_HOST = 4; +} + +// Provider is reusable as the element type of a flattened STAC properties.providers dataset field. +message Provider { + string name = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).required = true, + (buf.validate.field).string.min_len = 1 + ]; + string description = 2 [features.field_presence = EXPLICIT]; + repeated ProviderRole roles = 3 [(buf.validate.field).repeated.items.enum = { + defined_only: true + not_in: [0] + }]; + string url = 4 [features.field_presence = EXPLICIT]; +} + +// Link contains the STAC 1.1 Link Object fields used by the supported Items. +message Link { + string href = 1 [(buf.validate.field).string.min_len = 1]; + string rel = 2 [(buf.validate.field).string.min_len = 1]; + MediaType media_type = 3; + string title = 4 [features.field_presence = EXPLICIT]; + // Keys into the item-level Storage.schemes registry. + repeated string storage_refs = 5; + // Keys into the item-level Authentication.schemes registry. + repeated string auth_refs = 6; +} + +// Links contains the ordered Link Objects of a STAC entity. +message Links { + repeated Link links = 1; +} diff --git a/apis/datasets/stac/v1/processing.proto b/apis/datasets/stac/v1/processing.proto new file mode 100644 index 0000000..0d092f6 --- /dev/null +++ b/apis/datasets/stac/v1/processing.proto @@ -0,0 +1,15 @@ +// This file models the typed processing:software value from the STAC Processing 1.2 extension. The dynamic software +// names remain map keys instead of becoming generated dataset field names. +// https://github.com/stac-extensions/processing/blob/v1.2.0/README.md + +edition = "2023"; +package datasets.stac.v1; + +import "buf/validate/validate.proto"; + +option features.field_presence = IMPLICIT; + +// ProcessingSoftware maps each application or library name to its version, tag, commit, or equivalent identifier. +message ProcessingSoftware { + map versions = 1 [(buf.validate.field).map = {keys: {string: {min_len: 1}}}]; +} diff --git a/apis/datasets/stac/v1/product.proto b/apis/datasets/stac/v1/product.proto new file mode 100644 index 0000000..1e2607a --- /dev/null +++ b/apis/datasets/stac/v1/product.proto @@ -0,0 +1,42 @@ +// This file models the STAC Product 1.0 extension for Tilebox assets and generated dataset fields. Product type and +// timeliness category remain open provider vocabularies as required by the extension. +// https://github.com/stac-extensions/product/blob/v1.0.0/README.md + +edition = "2023"; +package datasets.stac.v1; + +import "buf/validate/validate.proto"; + +option features.field_presence = IMPLICIT; + +// ProductAcquisitionType contains the closed Product 1.0 product:acquisition_type vocabulary. +enum ProductAcquisitionType { + PRODUCT_ACQUISITION_TYPE_UNSPECIFIED = 0; + PRODUCT_ACQUISITION_TYPE_NOMINAL = 1; + PRODUCT_ACQUISITION_TYPE_CALIBRATION = 2; + PRODUCT_ACQUISITION_TYPE_OTHER = 3; +} + +// ProductProperties contains all Product 1.0 fields. In particular, type is an open string rather than an enum. +message ProductProperties { + option (buf.validate.message).cel = { + id: "product.timeliness_category" + message: "product timeliness_category requires timeliness" + expression: "!has(this.timeliness_category) || has(this.timeliness)" + }; + + string type = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + // ISO 8601 duration. This remains a string because calendar durations cannot always be represented by protobuf Duration. + string timeliness = 2 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + string timeliness_category = 3 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + ProductAcquisitionType acquisition_type = 4 [(buf.validate.field).enum.defined_only = true]; +} diff --git a/apis/datasets/stac/v1/sar.proto b/apis/datasets/stac/v1/sar.proto new file mode 100644 index 0000000..1211b3e --- /dev/null +++ b/apis/datasets/stac/v1/sar.proto @@ -0,0 +1,99 @@ +// This file models STAC SAR 1.3.2 metadata for Tilebox assets and generated dataset fields. Deprecated product types are +// normalized to the Product extension, while instrument_mode remains available for source compatibility. +// https://github.com/stac-extensions/sar/blob/v1.3.2/README.md + +edition = "2023"; +package datasets.stac.v1; + +import "buf/validate/validate.proto"; + +option features.field_presence = IMPLICIT; + +// SARFrequencyBand contains the closed SAR 1.3.2 sar:frequency_band vocabulary. +enum SARFrequencyBand { + SAR_FREQUENCY_BAND_UNSPECIFIED = 0; + SAR_FREQUENCY_BAND_P = 1; + SAR_FREQUENCY_BAND_L = 2; + SAR_FREQUENCY_BAND_S = 3; + SAR_FREQUENCY_BAND_C = 4; + SAR_FREQUENCY_BAND_X = 5; + SAR_FREQUENCY_BAND_KU = 6; + SAR_FREQUENCY_BAND_K = 7; + SAR_FREQUENCY_BAND_KA = 8; +} + +// SARPolarization contains the closed SAR 1.3.2 sar:polarizations element vocabulary. +enum SARPolarization { + SAR_POLARIZATION_UNSPECIFIED = 0; + SAR_POLARIZATION_HH = 1; + SAR_POLARIZATION_VV = 2; + SAR_POLARIZATION_HV = 3; + SAR_POLARIZATION_VH = 4; + SAR_POLARIZATION_LH = 5; + SAR_POLARIZATION_LV = 6; + SAR_POLARIZATION_RH = 7; + SAR_POLARIZATION_RV = 8; + SAR_POLARIZATION_CH = 9; + SAR_POLARIZATION_CV = 10; +} + +// SARObservationDirection contains the closed SAR 1.3.2 sar:observation_direction vocabulary. +enum SARObservationDirection { + SAR_OBSERVATION_DIRECTION_UNSPECIFIED = 0; + SAR_OBSERVATION_DIRECTION_LEFT = 1; + SAR_OBSERVATION_DIRECTION_RIGHT = 2; +} + +// SARProperties contains the SAR 1.3.2 fields for an Asset. Item-property fields remain flattened in generated dataset +// schemas and may reuse these enum types. Deprecated sar:product_type is normalized to Product 1.0. +message SARProperties { + repeated SARPolarization polarizations = 1 [(buf.validate.field).repeated.items.enum = { + defined_only: true + not_in: [0] + }]; + // Open provider- and mission-specific vocabulary. Deprecated by SAR 1.3.2 but retained for source compatibility. + string instrument_mode = 2 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + SARFrequencyBand frequency_band = 3 [(buf.validate.field).enum.defined_only = true]; + // Center frequency in gigahertz. + double center_frequency = 4 [ + features.field_presence = EXPLICIT, + (buf.validate.field).double.gt = 0 + ]; + // Range bandwidth in gigahertz. + double bandwidth = 5 [ + features.field_presence = EXPLICIT, + (buf.validate.field).double.gt = 0 + ]; + // Resolution and pixel spacing values are in metres. + double resolution_range = 6 [ + features.field_presence = EXPLICIT, + (buf.validate.field).double.gte = 0 + ]; + double resolution_azimuth = 7 [ + features.field_presence = EXPLICIT, + (buf.validate.field).double.gte = 0 + ]; + double pixel_spacing_range = 8 [ + features.field_presence = EXPLICIT, + (buf.validate.field).double.gte = 0 + ]; + double pixel_spacing_azimuth = 9 [ + features.field_presence = EXPLICIT, + (buf.validate.field).double.gte = 0 + ]; + uint64 looks_range = 10 [features.field_presence = EXPLICIT]; + uint64 looks_azimuth = 11 [features.field_presence = EXPLICIT]; + double looks_equivalent_number = 12 [ + features.field_presence = EXPLICIT, + (buf.validate.field).double.gte = 0 + ]; + SARObservationDirection observation_direction = 13 [(buf.validate.field).enum.defined_only = true]; + uint64 relative_burst = 14 [ + features.field_presence = EXPLICIT, + (buf.validate.field).uint64.gte = 1 + ]; + repeated string beam_ids = 15; +} diff --git a/apis/datasets/stac/v1/satellite.proto b/apis/datasets/stac/v1/satellite.proto new file mode 100644 index 0000000..17cb090 --- /dev/null +++ b/apis/datasets/stac/v1/satellite.proto @@ -0,0 +1,54 @@ +// This file models STAC Satellite 1.2 orbit metadata for Tilebox assets and generated dataset fields, from lightweight +// orbit-state categories through optional timestamped position and velocity vectors. +// https://github.com/stac-extensions/sat/blob/v1.2.0/README.md + +edition = "2023"; +package datasets.stac.v1; + +import "buf/validate/validate.proto"; +import "google/protobuf/timestamp.proto"; + +option features.field_presence = IMPLICIT; + +// SatelliteOrbitState contains the closed Satellite 1.2 sat:orbit_state vocabulary. It describes the acquisition's +// orbit direction or class; it is distinct from the timestamped position and velocity samples in orbit_state_vectors. +enum SatelliteOrbitState { + SATELLITE_ORBIT_STATE_UNSPECIFIED = 0; + SATELLITE_ORBIT_STATE_ASCENDING = 1; + SATELLITE_ORBIT_STATE_DESCENDING = 2; + SATELLITE_ORBIT_STATE_GEOSTATIONARY = 3; + SATELLITE_ORBIT_STATE_CROSSING = 4; +} + +// SatelliteOrbitStateVector models one entry in the Satellite 1.2 sat:orbit_state_vectors map. +message SatelliteOrbitStateVector { + google.protobuf.Timestamp datetime = 1 [(buf.validate.field).required = true]; + // ECI position x/y/z in metres, velocity x/y/z in metres per second, and optional acceleration x/y/z in metres per + // second squared. The order matches the STAC JSON array. + repeated double values = 2 [(buf.validate.field).repeated = { + min_items: 6 + max_items: 9 + }]; +} + +// SatelliteProperties contains all Satellite 1.2 fields for an Asset. Item-property fields remain flattened in generated +// dataset schemas and may reuse SatelliteOrbitState and SatelliteOrbitStateVector. +message SatelliteProperties { + string platform_international_designator = 1 [features.field_presence = EXPLICIT]; + SatelliteOrbitState orbit_state = 2 [(buf.validate.field).enum.defined_only = true]; + uint64 absolute_orbit = 3 [ + features.field_presence = EXPLICIT, + (buf.validate.field).uint64.gte = 1 + ]; + uint64 relative_orbit = 4 [ + features.field_presence = EXPLICIT, + (buf.validate.field).uint64.gte = 1 + ]; + uint64 orbit_cycle = 5 [ + features.field_presence = EXPLICIT, + (buf.validate.field).uint64.gte = 1 + ]; + repeated SatelliteOrbitStateVector orbit_state_vectors = 6; + google.protobuf.Timestamp anx_datetime = 7; + string acquisition_station = 8 [features.field_presence = EXPLICIT]; +} diff --git a/apis/datasets/stac/v1/storage.proto b/apis/datasets/stac/v1/storage.proto new file mode 100644 index 0000000..0579641 --- /dev/null +++ b/apis/datasets/stac/v1/storage.proto @@ -0,0 +1,61 @@ +// This file models the STAC Storage 2.0 extension registry independently from the concrete hrefs stored on assets and +// links. Asset and link storage_refs contain keys into Storage.schemes. +// https://github.com/stac-extensions/storage/blob/v2.0.0/README.md + +edition = "2023"; +package datasets.stac.v1; + +import "buf/validate/validate.proto"; + +option features.field_presence = IMPLICIT; + +// Storage stores the Storage 2.0 storage:schemes registry. +message Storage { + map schemes = 1; +} + +// KnownStorageType is Tilebox's compact dictionary for common open Storage 2.0 type strings. +enum KnownStorageType { + KNOWN_STORAGE_TYPE_UNSPECIFIED = 0; + // aws-s3 + KNOWN_STORAGE_TYPE_AWS_S3 = 1; + // custom-s3 + KNOWN_STORAGE_TYPE_CUSTOM_S3 = 2; + // ms-azure + KNOWN_STORAGE_TYPE_MICROSOFT_AZURE = 3; + // Tilebox convention exported as the custom Storage type string `google-cloud-storage`. + KNOWN_STORAGE_TYPE_GOOGLE_CLOUD_STORAGE = 4; +} + +// StorageScheme describes a storage provider independently of concrete asset hrefs. +message StorageScheme { + option (buf.validate.message).oneof = { + fields: [ + "known_type", + "custom_type" + ] + required: true + }; + + // Exactly one compact known type or exact custom Storage 2.0 type string is set. + KnownStorageType known_type = 1 [ + features.field_presence = EXPLICIT, + (buf.validate.field).enum = { + defined_only: true + not_in: [0] + } + ]; + string custom_type = 2 [ + features.field_presence = EXPLICIT, + (buf.validate.field).string.min_len = 1 + ]; + + // Provider endpoint/API URI or URI template, not an asset href prefix. + string platform = 3 [(buf.validate.field).string.min_len = 1]; + string title = 4 [features.field_presence = EXPLICIT]; + string description = 5 [features.field_presence = EXPLICIT]; + string region = 6 [features.field_presence = EXPLICIT]; + bool requester_pays = 7 [features.field_presence = EXPLICIT]; + // Maps the provider-specific `class` property used by the Copernicus example. + string storage_class = 8 [features.field_presence = EXPLICIT]; +} From cd617eb1b54f387bd73ce98b684d5137432e1345 Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Thu, 16 Jul 2026 14:49:47 +0200 Subject: [PATCH 2/2] Document three-valued boolean logic for query expressions --- apis/datasets/v1/data_access.proto | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apis/datasets/v1/data_access.proto b/apis/datasets/v1/data_access.proto index 4f4d117..00c9a20 100644 --- a/apis/datasets/v1/data_access.proto +++ b/apis/datasets/v1/data_access.proto @@ -47,10 +47,17 @@ message QueryFilters { // Additional expressions over fields marked queryable in the dataset schema. // All top-level expressions are combined with each other and with the interval and spatial filters using logical AND. // Use a nested LogicalExpression for explicit OR, NOT, or grouped boolean logic. - repeated FilterExpression expressions = 4 [(buf.validate.field).repeated.max_items = 100]; + // Protovalidate limits each list of expressions to 64 items. Before compiling a query, the server must also enforce: + // - at most 256 FilterExpression nodes in total across all top-level and nested expressions; + // - a maximum expression depth of 8, where a top-level FilterExpression has depth 1 and each nested operand adds 1; + // - every referenced field exists and is marked queryable; and + // - every comparison operator and literal kind is valid for the referenced field's descriptor. + repeated FilterExpression expressions = 4 [(buf.validate.field).repeated.max_items = 64]; } // FilterExpression is a typed filter expression compatible with the Basic CQL2 property-to-literal subset. +// Expressions use three-valued Boolean logic compatible with SQL and CQL2: true, false, and null (unknown). A datapoint +// matches only when the complete filter expression evaluates to true; false and null results are excluded. message FilterExpression { option (buf.validate.message).oneof = { fields: [ @@ -67,7 +74,8 @@ message FilterExpression { FieldNullCheck is_null = 3; } -// LogicalExpression combines nested filter expressions. +// LogicalExpression combines nested filter expressions using SQL- and CQL2-compatible three-valued Boolean logic. +// AND, OR, and NOT propagate null according to their standard truth tables; in particular, NOT(null) evaluates to null. message LogicalExpression { option (buf.validate.message).cel = { id: "logical_expression.operand_arity" @@ -83,7 +91,7 @@ message LogicalExpression { // Operands for the logical operation. AND and OR require at least two; NOT requires exactly one. repeated FilterExpression operands = 2 [(buf.validate.field).repeated = { min_items: 1 - max_items: 100 + max_items: 64 }]; } @@ -96,6 +104,9 @@ enum LogicalOperator { } // FieldComparison compares a queryable dataset field to a typed literal value. +// Every comparison evaluates to null (unknown) when the field is absent or explicitly null, including NOT_EQUAL. +// Consequently, both `field != value` and `NOT(field == value)` require the field to be present. To include absent or +// null fields, explicitly combine the comparison with FieldNullCheck using OR. message FieldComparison { // Name of a top-level field in the dataset schema. string field_name = 1 [(buf.validate.field).string = { @@ -123,8 +134,9 @@ enum FieldComparisonOperator { FIELD_COMPARISON_OPERATOR_GREATER_THAN_OR_EQUAL = 6; } -// FieldNullCheck matches datapoints for which a queryable field is absent or explicitly null. -// IS NOT NULL is represented by wrapping this expression in LOGICAL_OPERATOR_NOT. +// FieldNullCheck matches datapoints for which a queryable field is absent or explicitly null. These states are +// equivalent for filtering. IS NOT NULL is represented by wrapping this expression in LOGICAL_OPERATOR_NOT and matches +// only datapoints for which the field is present and non-null. message FieldNullCheck { // Name of a top-level field in the dataset schema. string field_name = 1 [(buf.validate.field).string = {