diff --git a/Cargo.lock b/Cargo.lock index 8cc39bc..6a0243e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1326,6 +1326,20 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-loader" version = "0.1.0" @@ -1951,6 +1965,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -2944,6 +2964,7 @@ dependencies = [ "axum", "axum-extra", "clap", + "dashmap", "data-loader", "dotenvy", "dyn-clone", @@ -2957,6 +2978,7 @@ dependencies = [ "ogcapi-processes", "ogcapi-types", "openapiv3", + "reqwest", "schemars 1.1.0", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 933153e..14ec7b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ keywords = ["geography", "geo", "geospatial", "gis", "api"] [workspace.dependencies] anyhow = "1.0" -geojson = "0.24.2" +geojson = {version = "0.24.2", default-features = false } log = "0.4.27" serde = "1.0" serde_json = "1.0" diff --git a/examples/cite-service/Cargo.toml b/examples/cite-service/Cargo.toml index c5bc1a2..4f99044 100644 --- a/examples/cite-service/Cargo.toml +++ b/examples/cite-service/Cargo.toml @@ -13,4 +13,5 @@ ogcapi = { path = "../../ogcapi", version = "0.3", default-features = false, fea "common", "features", "processes", + "tiles", ] } diff --git a/ogcapi-services/Cargo.toml b/ogcapi-services/Cargo.toml index fa5f3e6..b687898 100644 --- a/ogcapi-services/Cargo.toml +++ b/ogcapi-services/Cargo.toml @@ -20,19 +20,21 @@ edr = ["ogcapi-types/edr", "ogcapi-drivers/edr"] processes = ["ogcapi-types/processes", "ogcapi-drivers/processes", "ogcapi-processes", "dyn-clone", "schemars", "mail-builder"] stac = ["ogcapi-types/stac", "ogcapi-drivers/stac"] styles = ["ogcapi-types/styles", "ogcapi-drivers/styles"] -tiles = ["ogcapi-types/tiles", "ogcapi-drivers/tiles"] +tiles = ["ogcapi-types/tiles", "ogcapi-drivers/tiles", "dashmap", "reqwest"] [dependencies] anyhow = { workspace = true } axum = { version = "0.8.7", features = ["multipart"] } axum-extra = { version = "0.12.2" } clap = { version = "4.5", features = ["derive", "env"] } +dashmap = { version = "6.1", optional = true } dyn-clone = { version = "1.0", optional = true } dotenvy = "0.15.7" futures = "0.3.31" hyper = { version = "1.8", features = ["full"] } mail-builder = { version = "0.4.4", optional = true } openapiv3 = "2.2" +reqwest = { version = "0.12", optional = true, default-features = false, features = ["json"] } schemars = { version = "1.1", optional = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/ogcapi-services/assets/openapi/openapi.yaml b/ogcapi-services/assets/openapi/openapi.yaml index 56aeb0f..09312a7 100644 --- a/ogcapi-services/assets/openapi/openapi.yaml +++ b/ogcapi-services/assets/openapi/openapi.yaml @@ -20,6 +20,8 @@ tags: description: essential characteristics of this API - name: Data description: access to data (features) + - name: Tiling Schemes + - name: Vector Tiles - name: Processes description: OGC API Processes. - name: Jobs @@ -32,11 +34,11 @@ paths: conformance statements for this API. operationId: getLandingPage responses: - 200: + "200": $ref: "#/components/responses/LandingPage" - 400: + "400": $ref: "#/components/responses/BadRequest" - 500: + "500": $ref: "#/components/responses/ServerError" summary: Landing page tags: @@ -45,9 +47,9 @@ paths: get: description: This document responses: - 200: + "200": $ref: "#/components/responses/Success" - 400: + "400": $ref: "#/components/responses/BadRequest" default: $ref: "#/components/responses/ServerError" @@ -61,11 +63,11 @@ paths: server conforms to. operationId: getConformanceDeclaration responses: - 200: + "200": $ref: "#/components/responses/ConformanceDeclaration" - 400: + "400": $ref: "#/components/responses/BadRequest" - 500: + "500": $ref: "#/components/responses/ServerError" summary: API conformance definition tags: @@ -77,9 +79,9 @@ paths: summary: the feature collections in the dataset operationId: getCollections responses: - 200: + "200": $ref: "#/components/schemas/collections" - 500: + "500": $ref: "#/components/responses/ServerError" /collections/{collectionId}: get: @@ -90,11 +92,11 @@ paths: parameters: - $ref: "#/components/parameters/collectionId" responses: - 200: + "200": $ref: "#/components/schemas/collectionDesc" - 404: + "404": $ref: "#/components/responses/NotFound" - 500: + "500": $ref: "#/components/responses/ServerError" /collections/{collectionId}/items: get: @@ -118,13 +120,13 @@ paths: - $ref: "#/components/parameters/datetime" - $ref: "#/components/parameters/crs" responses: - 200: + "200": $ref: "#/components/responses/FeatureCollection" - 400: + "400": $ref: "#/components/responses/BadRequest" - 404: + "404": $ref: "#/components/responses/NotFound" - 500: + "500": $ref: "#/components/responses/ServerError" /collections/{collectionId}/items/{featureId}: get: @@ -142,11 +144,185 @@ paths: - $ref: "#/components/parameters/featureId" - $ref: "#/components/parameters/crs" responses: - 200: + "200": $ref: "#/components/responses/Feature" - 404: + "404": $ref: "#/components/responses/NotFound" - 500: + "500": + $ref: "#/components/responses/ServerError" + + # Shared Tile Matrix Set definitions + /tileMatrixSets: + get: + tags: + - Tiling Schemes + summary: Retrieve the list of available tiling schemes (tile matrix sets) + operationId: getTileMatrixSetsList + # parameters: + # - $ref: "#/components/parameters/f-metadata" + responses: + "200": + $ref: "#/components/responses/TileMatrixSetsList" + "406": + $ref: "#/components/responses/NotAcceptable" + "500": + $ref: "#/components/responses/ServerError" + /tileMatrixSets/{tileMatrixSetId}: + get: + tags: + - Tiling Schemes + summary: Retrieve the definition of the specified tiling scheme (tile matrix set) + operationId: getTileMatrixSet + parameters: + - $ref: "#/components/parameters/tileMatrixSetId" + # - $ref: "#/components/parameters/f-metadata" + responses: + "200": + $ref: "#/components/responses/TileMatrixSet" + "404": + description: The requested tile matrix set id was not found + content: + application/json: + schema: + $ref: "#/components/schemas/exception" + "406": + $ref: "#/components/responses/NotAcceptable" + "500": + $ref: "#/components/responses/ServerError" + + # Vector Tiles: Data Set Tile Sets + /tiles: + get: + tags: + - Vector Tiles + summary: Retrieve a list of available vector tilesets for the dataset + operationId: .dataset.vector.getTileSetsList + # parameters: + # - $ref: "#/components/parameters/f-metadata" + responses: + "200": + $ref: "#/components/responses/TileSetsList" + "404": + $ref: "#/components/responses/NotFound" + "406": + $ref: "#/components/responses/NotAcceptable" + "500": + $ref: "#/components/responses/ServerError" + /tiles/{tileMatrixSetId}: + get: + tags: + - Vector Tiles + summary: Retrieve the vector tileset metadata for the whole dataset and the specified tiling scheme (tile matrix set) + operationId: .dataset.vector.getTileSet + parameters: + - $ref: "#/components/parameters/collections" + - $ref: "#/components/parameters/tileMatrixSetId" + # - $ref: "#/components/parameters/f-metadata" + responses: + "200": + $ref: "#/components/responses/TileSet" + "404": + $ref: "#/components/responses/NotFound" + "406": + $ref: "#/components/responses/NotAcceptable" + "500": + $ref: "#/components/responses/ServerError" + /tiles/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol}: + get: + tags: + - Vector Tiles + summary: Retrieve a vector tile including one or more collections from the dataset. + operationId: .dataset.vector.getTile + parameters: + - $ref: "#/components/parameters/tileMatrix" + - $ref: "#/components/parameters/tileRow" + - $ref: "#/components/parameters/tileCol" + - $ref: "#/components/parameters/datetime" + - $ref: "#/components/parameters/collections" + # - $ref: "#/components/parameters/subset" + - $ref: "#/components/parameters/crs" + # - $ref: "#/components/parameters/subset-crs" + - $ref: "#/components/parameters/tileMatrixSetId" + # - $ref: "#/components/parameters/f-vectorTile" + responses: + "200": + $ref: "#/components/responses/VectorTile" + "204": + $ref: "#/components/responses/Empty" + "404": + $ref: "#/components/responses/NotFound" + "406": + $ref: "#/components/responses/NotAcceptable" + "500": + $ref: "#/components/responses/ServerError" + + # Vector Tiles: Collection Tile Sets + /collections/{collectionId}/tiles: + get: + tags: + - Vector Tiles + summary: Retrieve a list of available vector tilesets for the specified collection. + operationId: .collection.vector.getTileSetsList + parameters: + - $ref: "#/components/parameters/collectionId" + # - $ref: "#/components/parameters/f-metadata" + responses: + "200": + $ref: "#/components/responses/TileSetsList" + "404": + $ref: "#/components/responses/NotFound" + "406": + $ref: "#/components/responses/NotAcceptable" + "500": + $ref: "#/components/responses/ServerError" + /collections/{collectionId}/tiles/{tileMatrixSetId}: + get: + tags: + - Vector Tiles + summary: Retrieve the vector tileset metadata for the specified collection and tiling scheme (tile matrix set) + operationId: .collection.vector.getTileSet + parameters: + - $ref: "#/components/parameters/collectionId" + - $ref: "#/components/parameters/collections" + - $ref: "#/components/parameters/tileMatrixSetId" + # - $ref: "#/components/parameters/f-metadata" + responses: + "200": + $ref: "#/components/responses/TileSet" + "404": + $ref: "#/components/responses/NotFound" + "406": + $ref: "#/components/responses/NotAcceptable" + "500": + $ref: "#/components/responses/ServerError" + /collections/{collectionId}/tiles/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol}: + get: + tags: + - Vector Tiles + summary: Retrieve a vector tile from a collection. + operationId: .collection.vector.getTile + parameters: + - $ref: "#/components/parameters/tileMatrix" + - $ref: "#/components/parameters/tileRow" + - $ref: "#/components/parameters/tileCol" + - $ref: "#/components/parameters/datetime" + - $ref: "#/components/parameters/collectionId" + - $ref: "#/components/parameters/collections" + # - $ref: "#/components/parameters/subset" + - $ref: "#/components/parameters/crs" + # - $ref: "#/components/parameters/subset-crs" + - $ref: "#/components/parameters/tileMatrixSetId" + # - $ref: "#/components/parameters/f-vectorTile" + responses: + "200": + $ref: "#/components/responses/VectorTile" + "204": + $ref: "#/components/responses/Empty" + "404": + $ref: "#/components/responses/NotFound" + "406": + $ref: "#/components/responses/NotAcceptable" + "500": $ref: "#/components/responses/ServerError" # OGC API - Processes - Part 1: Core @@ -161,7 +337,7 @@ paths: tags: - Processes responses: - 200: + "200": $ref: "#/components/responses/ProcessList" /processes/{processID}: @@ -179,9 +355,9 @@ paths: parameters: - $ref: "#/components/parameters/processID-path" responses: - 200: + "200": $ref: "#/components/responses/ProcessDescription" - 404: + "404": $ref: "#/components/responses/NotFound" /processes/{processID}/execution: @@ -208,15 +384,15 @@ paths: schema: $ref: "#/components/schemas/execute" responses: - 200: + "200": $ref: "#/components/responses/ExecuteSync" - 201: + "201": $ref: "#/components/responses/ExecuteAsync" - 204: + "204": $ref: "#/components/responses/Empty" - 404: + "404": $ref: "#/components/responses/NotFound" - 500: + "500": $ref: "#/components/responses/ServerError" # callbacks: # jobCompleted: @@ -226,9 +402,9 @@ paths: # content: # application/json: # schema: - # $ref: "../../schemas/processes-core/results.yaml" + # $ref: "#/components/schemas/results" # responses: - # 200: + # "200": # description: Results received successfully # OGC API - Processes - Part 1: Core (Jobs, required for Async execution) @@ -251,9 +427,9 @@ paths: - $ref: "#/components/parameters/minDuration" - $ref: "#/components/parameters/maxDuration" responses: - 200: + "200": $ref: "#/components/responses/JobList" - 404: + "404": $ref: "#/components/responses/NotFound" /jobs/{jobID}: @@ -269,11 +445,11 @@ paths: parameters: - $ref: "#/components/parameters/jobID" responses: - 200: + "200": $ref: "#/components/responses/Status" - 404: + "404": $ref: "#/components/responses/NotFound" - 500: + "500": $ref: "#/components/responses/ServerError" delete: summary: cancel a job execution, remove a finished job @@ -287,11 +463,11 @@ paths: parameters: - $ref: "#/components/parameters/jobID" responses: - 200: + "200": $ref: "#/components/responses/Status" - 404: + "404": $ref: "#/components/responses/NotFound" - 500: + "500": $ref: "#/components/responses/ServerError" /jobs/{jobID}/results: @@ -308,11 +484,11 @@ paths: - $ref: "#/components/parameters/jobID" - $ref: "#/components/parameters/prefer-header-results" responses: - 200: + "200": $ref: "#/components/responses/Results" - 404: + "404": $ref: "#/components/responses/NotFound" - 500: + "500": $ref: "#/components/responses/ServerError" components: @@ -451,6 +627,65 @@ components: default: 10 style: form explode: false + tileMatrixSetId: + name: tileMatrixSetId + in: path + description: Identifier for a supported TileMatrixSet + required: true + allowEmptyValue: false + schema: + type: string + enum: + - "WebMercatorQuad" + - "WorldCRS84Quad" + - "GNOSISGlobalGrid" + - "WorldMercatorWGS84Quad" + collections: + name: collections + in: query + style: form + description: |- + The collections that should be included in the response. The parameter value is a comma-separated list of collection identifiers. + If the parameters is missing, some or all collections will be included. The collection will be rendered in the order specified, + with the last one showing on top, unless the priority is overridden by styling rules. + required: false + explode: false + schema: + type: array + items: + type: string + tileMatrix: + name: tileMatrix + in: path + description: |- + Identifier selecting one of the scales defined in the TileMatrixSet and representing the scaleDenominator the tile. For example, + Ireland is fully within the Tile at WebMercatorQuad tileMatrix=5, tileRow=10 and tileCol=15. + required: true + schema: + type: string + example: "5" + tileRow: + name: tileRow + in: path + description: |- + Row index of the tile on the selected TileMatrix. It cannot exceed the MatrixWidth-1 for the selected TileMatrix. + For example, Ireland is fully within the Tile at WebMercatorQuad tileMatrix=5, tileRow=10 and tileCol=15. + required: true + schema: + minimum: 0 + type: integer + example: 10 + tileCol: + name: tileCol + in: path + description: |- + Column index of the tile on the selected TileMatrix. It cannot exceed the MatrixHeight-1 for the selected TileMatrix. + For example, Ireland is fully within the Tile at WebMercatorQuad tileMatrix=5, tileRow=10 and tileCol=15. + required: true + schema: + minimum: 0 + type: integer + example: 15 processID-path: name: processID in: path @@ -462,7 +697,8 @@ components: description: |- Indicates client preferences, including whether the client is capable of asynchronous processing. A `respond-async` preference indicates a preference for asynchronous processing. - A `wait: s` preference indicates that the client prefers to wait up to x seconds to receive a reponse synchronously before the server falls back to asynchronous processing. + A `wait: s` preference indicates that the client prefers to wait up to x seconds to receive a + reponse synchronously before the server falls back to asynchronous processing. name: Prefer schema: type: string @@ -510,7 +746,7 @@ components: description: local identifier of a job required: true schema: - type: string + type: string prefer-header-results: in: header description: |- @@ -573,6 +809,69 @@ components: application/geo+json: schema: $ref: "#/components/schemas/featureGeoJSON" + TileMatrixSetsList: + description: List of tile matrix sets (tiling schemes). + content: + application/json: + schema: + type: object + properties: + tileMatrixSets: + type: array + items: + $ref: "#/components/schemas/tileMatrixSet-item" + text/html: + schema: + type: string + TileMatrixSet: + description: tile matrix set + content: + application/json: + schema: + $ref: "#/components/schemas/tileMatrixSet" + TileSetsList: + description: List of available tilesets. + content: + application/json: + schema: + type: object + required: + - tilesets + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + tilesets: + type: array + items: + $ref: "#/components/schemas/tileSet-item" + text/html: + schema: + type: string + TileSet: + description: Description of the tileset + content: + application/json: + schema: + $ref: "#/components/schemas/tileSet" + text/html: + schema: + type: string + VectorTile: + description: A vector tile returned as a response. + content: + application/vnd.mapbox-vector-tile: + schema: + type: string + format: binary + application/geo+json: + schema: + allOf: + - format: geojson-feature-collection + # JSON Schema not supported in OpenAPI 3.0 + # - $ref: https://geojson.org/schema/FeatureCollection.json + - $ref: "#/components/schemas/featureCollectionGeoJSON" ProcessList: description: Information about the available processes content: @@ -584,7 +883,7 @@ components: content: application/json: schema: - $ref: "../../schemas/processes-core/process.yaml" + $ref: "#/components/schemas/process" ExecuteSync: description: Result of synchronous execution content: @@ -601,7 +900,7 @@ components: - type: boolean - type: string format: binary - - $ref: "../../schemas/processes-core/results.yaml" + - $ref: "#/components/schemas/results" image/png: schema: type: string @@ -620,7 +919,7 @@ components: - format: geojson-feature-collection # JSON Schema not supported in OpenAPI 3.0 # - $ref: https://geojson.org/schema/FeatureCollection.json - - $ref: "../../schemas/geojson/FeatureCollection.yaml" + - $ref: "#/components/schemas/featureCollectionGeoJSON" ExecuteAsync: description: Started asynchronous execution. Created job. headers: @@ -635,7 +934,7 @@ components: content: application/json: schema: - $ref: "../../schemas/processes-core/statusInfo.yaml" + $ref: "#/components/schemas/statusInfo" Empty: description: successful operation (no response body) JobList: @@ -643,19 +942,19 @@ components: content: application/json: schema: - $ref: "../../schemas/processes-core/jobList.yaml" + $ref: "#/components/schemas/jobList" Status: description: The status of a job. content: application/json: schema: - $ref: "../../schemas/processes-core/statusInfo.yaml" + $ref: "#/components/schemas/statusInfo" Results: description: The processing results of a job. content: application/json: schema: - $ref: "../../schemas/processes-core/results.yaml" + $ref: "#/components/schemas/results" Success: description: General Success response. BadRequest: @@ -676,6 +975,15 @@ components: text/html: schema: type: string + NotAcceptable: + description: Content negotiation failed. For example, the `Accept` header submitted in the request did not support any of the media types supported by the server for the requested resource. + content: + application/json: + schema: + $ref: "#/components/schemas/exception" + text/html: + schema: + type: string ServerError: description: A server error occurred. content: @@ -1196,6 +1504,698 @@ components: type: string format: date-time example: "2017-08-17T08:05:32Z" + dataType: + # This list may be extended (e.g. point clouds, meshes) + anyOf: + - type: string + - type: string + enum: + - map + - vector + - coverage + tileMatrixSet-item: + title: Tile Matrix Set Item + description: A minimal tile matrix set element for use within a list of tile matrix sets linking to a full definition. + type: object + required: + - links + properties: + id: + description: Optional local tile matrix set identifier, e.g. for use as unspecified `{tileMatrixSetId}` parameter. Implementation of 'identifier' + type: string + title: + description: Title of this tile matrix set, normally used for display to a human + type: string + uri: + description: Reference to an official source for this tileMatrixSet + type: string + format: uri + crs: + allOf: + - description: Coordinate Reference System (CRS) + - $ref: "#/components/schemas/crs" + links: + description: Links to related resources. A 'self' link to the tile matrix set definition is required. + type: array + items: + $ref: "#/components/schemas/link" + tileMatrixSet: + title: Tile Matrix Set Definition + description: + A definition of a tile matrix set following the Tile Matrix Set standard. + For tileset metadata, such a description (in `tileMatrixSet` property) is only required + for offline use, as an alternative to a link with a `http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme` + relation type. + type: object + required: + - crs + - tileMatrices + properties: + title: + description: Title of this tile matrix set, normally used for display to a human + type: string + description: + description: + Brief narrative description of this tile matrix set, normally available + for display to a human + type: string + keywords: + description: + Unordered list of one or more commonly used or formalized word(s) + or phrase(s) used to describe this tile matrix set + type: array + items: + type: string + id: + description: Tile matrix set identifier. Implementation of 'identifier' + type: string + uri: + description: Reference to an official source for this tileMatrixSet + type: string + format: uri + orderedAxes: + type: array + minItems: 1 + items: + type: string + crs: + allOf: + - description: Coordinate Reference System (CRS) + - $ref: "#/components/schemas/crs" + wellKnownScaleSet: + description: Reference to a well-known scale set + type: string + format: uri + boundingBox: + allOf: + - description: Minimum bounding rectangle surrounding the tile matrix set, in the supported CRS + - $ref: "#/components/schemas/2DBoundingBox" + tileMatrices: + type: array + description: Describes scale levels and its tile matrices + items: + $ref: "#/components/schemas/tileMatrix" + tileMatrix: + description: "A tile matrix, usually corresponding to a particular zoom level of a TileMatrixSet." + type: object + required: + - id + - scaleDenominator + - cellSize + - pointOfOrigin + - tileWidth + - tileHeight + - matrixWidth + - matrixHeight + properties: + title: + description: Title of this tile matrix, normally used for display to a + human + type: string + description: + description: + Brief narrative description of this tile matrix set, normally + available for display to a human + type: string + keywords: + description: + Unordered list of one or more commonly used or formalized word(s) + or phrase(s) used to describe this dataset + type: array + items: + type: string + id: + description: + Identifier selecting one of the scales defined in the TileMatrixSet + and representing the scaleDenominator the tile. Implementation of 'identifier' + type: string + scaleDenominator: + description: Scale denominator of this tile matrix + type: number + cellSize: + description: Cell size of this tile matrix + type: number + cornerOfOrigin: + description: + The corner of the tile matrix (_topLeft_ or _bottomLeft_) used + as the origin for numbering tile rows and columns. This corner is also a + corner of the (0, 0) tile. + type: string + enum: + - topLeft + - bottomLeft + default: topLeft + pointOfOrigin: + allOf: + - description: + Precise position in CRS coordinates of the corner of origin (e.g. + the top-left corner) for this tile matrix. This position is also a corner + of the (0, 0) tile. In previous version, this was 'topLeftCorner' and 'cornerOfOrigin' + did not exist. + - description: A 2D Point in the CRS indicated elsewhere + type: array + minItems: 2 + maxItems: 2 + items: + type: number + tileWidth: + type: number + description: Width of each tile of this tile matrix in pixels + format: integer + minimum: 1 + multipleOf: 1 + tileHeight: + type: number + description: Height of each tile of this tile matrix in pixels + format: integer + minimum: 1 + multipleOf: 1 + matrixHeight: + type: number + description: Width of the matrix (number of tiles in width) + format: integer + minimum: 1 + multipleOf: 1 + matrixWidth: + type: number + description: Height of the matrix (number of tiles in height) + format: integer + minimum: 1 + multipleOf: 1 + variableMatrixWidths: + description: Describes the rows that has variable matrix width + type: array + items: + description: Variable Matrix Width data structure + type: object + required: + - coalesce + - minTileRow + - maxTileRow + properties: + coalesce: + description: + Number of tiles in width that coalesce in a single tile for these + rows + type: number + format: integer + minimum: 2 + multipleOf: 1 + minTileRow: + description: + First tile row where the coalescence factor applies for this + tilematrix + type: number + format: integer + minimum: 0 + multipleOf: 1 + maxTileRow: + description: Last tile row where the coalescence factor applies for this tilematrix + type: number + format: integer + minimum: 0 + multipleOf: 1 + tileSet-item: + title: Tile Set Metadata item + description: A minimal tileset element for use within a list of tilesets linking to full description of those tilesets. + type: object + required: + - dataType + - links + - crs + properties: + title: + description: A title for this tileset + type: string + dataType: + allOf: + - description: Type of data represented in the tileset + - $ref: "#/components/schemas/dataType" + crs: + allOf: + - description: Coordinate Reference System (CRS) + - $ref: "#/components/schemas/crs" + tileMatrixSetURI: + description: + Reference to a Tile Matrix Set on an offical source for Tile Matrix + Sets such as the OGC NA definition server (http://www.opengis.net/def/tms/). + Required if the tile matrix set is registered on an open official source. + type: string + format: uri + links: + description: Links to related resources. A 'self' link to the tileset + as well as a 'http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme' link + to a definition of the TileMatrixSet are required. + type: array + items: + $ref: "#/components/schemas/link" + tileSet: + title: Tile Set Metadata + description: + A resource describing a tileset based on the OGC TileSet Metadata Standard. + At least one of the 'TileMatrixSet', or a link with 'rel' http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme + type: object + required: [dataType, crs, links] + properties: + title: + description: A title for this tileset + type: string + description: + description: Brief narrative description of this tile set + type: string + dataType: + allOf: + - description: Type of data represented in the tileset + - $ref: "#/components/schemas/dataType" + crs: + allOf: + - description: Coordinate Reference System (CRS) + - $ref: "#/components/schemas/crs" + tileMatrixSetURI: + description: + Reference to a Tile Matrix Set on an offical source for Tile Matrix + Sets such as the OGC NA definition server (http://www.opengis.net/def/tms/). + Required if the tile matrix set is registered on an open official source. + type: string + format: uri + links: + description: + "Links to related resources. Possible link 'rel' values are: 'http://www.opengis.net/def/rel/ogc/1.0/dataset' + for a URL pointing to the dataset, 'item' for a URL template to get a tile; + 'alternate' for a URL pointing to another representation of the TileSetMetadata + (e.g a TileJSON file); 'http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme' + for a definition of the TileMatrixSet; 'http://www.opengis.net/def/rel/ogc/1.0/geodata' + for pointing to a single collection (if the tileset represents a single collection)" + type: array + items: + $ref: "#/components/schemas/link" + tileMatrixSetLimits: + description: + Limits for the TileRow and TileCol values for each TileMatrix in + the tileMatrixSet. If missing, there are no limits other that the ones imposed + by the TileMatrixSet. If present the TileMatrices listed are limited and the + rest not available at all + type: array + items: + title: TileMatrixLimits + description: + The limits for an individual tile matrix of a TileSet's TileMatrixSet, + as defined in the OGC 2D TileMatrixSet and TileSet Metadata Standard + type: object + required: + - tileMatrix + - minTileRow + - maxTileRow + - minTileCol + - maxTileCol + properties: + tileMatrix: + type: string + minTileRow: + type: integer + minimum: 0 + maxTileRow: + type: integer + minimum: 0 + minTileCol: + type: integer + minimum: 0 + maxTileCol: + type: integer + minimum: 0 + epoch: + description: Epoch of the Coordinate Reference System (CRS) + type: number + layers: + minItems: 1 + type: array + items: + $ref: "#/components/schemas/geospatialData" + boundingBox: + allOf: + - description: Minimum bounding rectangle surrounding the tile matrix set, in the supported CRS + - $ref: "#/components/schemas/2DBoundingBox" + centerPoint: + allOf: + - description: + Location of a tile that nicely represents the tileset. Implementations + may use this center value to set the default location or to present a representative + tile in a user interface + - type: object + required: + - coordinates + properties: + coordinates: + type: array + minItems: 2 + maxItems: 2 + items: + type: number + crs: + allOf: + - description: Coordinate Reference System (CRS) of the coordinates + - $ref: "#/components/schemas/crs" + tileMatrix: + description: TileMatrix identifier associated with the scaleDenominator + type: string + scaleDenominator: + description: Scale denominator of the tile matrix selected + type: number + cellSize: + description: Cell size of the tile matrix selected + type: number + style: + allOf: + - description: Style involving all layers used to generate the tileset + - $ref: "#/components/schemas/style" + attribution: + description: Short reference to recognize the author or provider + type: string + license: + description: License applicable to the tiles + type: string + accessConstraints: + description: + Restrictions on the availability of the Tile Set that the user needs + to be aware of before using or redistributing the Tile Set + type: string + default: unclassified + enum: + - unclassified + - restricted + - confidential + - secret + - topSecret + keywords: + description: keywords about this tileset + type: array + items: + type: string + version: + description: + Version of the Tile Set. Changes if the data behind the tiles has + been changed + type: string + created: + allOf: + - description: When the Tile Set was first produced + - $ref: "#/components/schemas/timeStamp" + updated: + allOf: + - description: Last Tile Set change/revision + - $ref: "#/components/schemas/timeStamp" + pointOfContact: + description: + Useful information to contact the authors or custodians for the Tile + Set + type: string + mediaTypes: + description: Media types available for the tiles + type: array + items: + type: string + geospatialData: + type: object + required: + - id + - dataType + properties: + title: + description: Title of this tile matrix set, normally used for display to a human + type: string + description: + description: + Brief narrative description of this tile matrix set, normally + available for display to a human + type: string + keywords: + description: + Unordered list of one or more commonly used or formalized word(s) + or phrase(s) used to describe this layer + type: string + id: + description: Unique identifier of the Layer. Implementation of 'identifier' + type: string + dataType: + allOf: + - description: Type of data represented in the layer + - $ref: "#/components/schemas/dataType" + geometryDimension: + description: "The geometry dimension of the features shown in this layer (0: points, 1: curves, 2: surfaces, 3: solids), unspecified: mixed or unknown" + type: integer + minimum: 0 + maximum: 3 + featureType: + description: Feature type identifier. Only applicable to layers of datatype 'geometries' + type: string + attribution: + description: Short reference to recognize the author or provider + type: string + license: + description: License applicable to the tiles + type: string + pointOfContact: + description: + Useful information to contact the authors or custodians for the + layer (e.g. e-mail address, a physical address, phone numbers, etc) + type: string + publisher: + description: Organization or individual responsible for making the layer available + type: string + theme: + description: Category where the layer can be grouped + type: string + crs: + allOf: + - description: Coordinate Reference System (CRS) + - $ref: "#/components/schemas/crs" + epoch: + description: Epoch of the Coordinate Reference System (CRS) + type: number + minScaleDenominator: + description: Minimum scale denominator for usage of the layer + type: number + maxScaleDenominator: + description: Maximum scale denominator for usage of the layer + type: number + minCellSize: + description: Minimum cell size for usage of the layer + type: number + maxCellSize: + description: Maximum cell size for usage of the layer + type: number + maxTileMatrix: + description: TileMatrix identifier associated with the minScaleDenominator + type: string + minTileMatrix: + description: TileMatrix identifier associated with the maxScaleDenominator + type: string + boundingBox: + allOf: + - description: Minimum bounding rectangle surrounding the layer + - $ref: "#/components/schemas/2DBoundingBox" + created: + allOf: + - description: When the layer was first produced + - $ref: "#/components/schemas/timeStamp" + updated: + allOf: + - description: Last layer change/revision + - $ref: "#/components/schemas/timeStamp" + style: + allOf: + - description: Style used to generate the layer in the tileset + - $ref: "#/components/schemas/style" + geoDataClasses: + description: + URI identifying a class of data contained in this layer (useful + to determine compatibility with styles or processes) + type: array + items: + type: string + propertiesSchema: + allOf: + - description: + Properties represented by the features in this layer. Can be + the attributes of a feature dataset (datatype=geometries) or the rangeType + of a coverage (datatype=coverage) + - $ref: "#/components/schemas/propertiesSchema" + links: + description: + "Links related to this layer. Possible link 'rel' values are: + 'geodata' for a URL pointing to the collection of geospatial data." + type: array + minItems: 1 + items: + $ref: "#/components/schemas/link" + style: + type: object + required: + - id + properties: + id: + description: An identifier for this style. Implementation of 'identifier' + type: string + title: + description: A title for this style + type: string + description: + description: Brief narrative description of this style + type: string + keywords: + description: keywords about this style + type: array + items: + type: string + links: + description: + "Links to style related resources. Possible link 'rel' values + are: 'style' for a URL pointing to the style description, 'styleSpec' + for a URL pointing to the specification or standard used to define the style." + type: array + minItems: 1 + items: + $ref: "#/components/schemas/link" + propertiesSchema: + description: + Attributes of the features or rangetypes of a coverage. Defined by + a subset of the JSON Schema for the properties of a feature + type: object + required: + - type + - properties + properties: + type: + type: string + enum: + - object + required: + description: "Implements 'multiplicity' by citing property 'name' defined as 'additionalProperties'" + type: array + minItems: 1 + items: + type: string + properties: + type: object + default: {} + additionalProperties: + description: "No property names are defined but any property name they should be described by JSON Schema. So 'additionalProperties' implements 'name'." + type: object + properties: + title: + type: string + description: + description: "Implements 'description'" + type: string + type: + type: string + enum: + - array + - boolean + - integer + - "null" + - number + - object + - string + enum: + description: "Implements 'acceptedValues'" + type: array + minItems: 1 + items: {} + uniqueItems: true + format: + description: "Complements implementation of 'type'" + type: string + contentMediaType: + description: "Implements 'mediaType'" + type: string + maximum: + description: "Implements 'range'" + type: number + exclusiveMaximum: + description: "Implements 'range'" + type: number + minimum: + description: "Implements 'range'" + type: number + exclusiveMinimum: + description: "Implements 'range'" + type: number + pattern: + type: string + format: regex + maxItems: + description: "Implements 'upperMultiplicity'" + type: integer + minimum: 0 + minItems: + description: "Implements 'lowerMultiplicity'" + type: integer + default: 0 + minimum: 0 + observedProperty: + type: string + observedPropertyURI: + type: string + format: uri + uom: + type: string + uomURI: + type: string + format: uri + 2DBoundingBox: + description: Minimum bounding rectangle surrounding a 2D resource in the CRS indicated elsewhere + type: object + required: + - lowerLeft + - upperRight + properties: + lowerLeft: + $ref: "#/components/schemas/2DPoint" + upperRight: + $ref: "#/components/schemas/2DPoint" + crs: + $ref: "#/components/schemas/crs" + orderedAxes: + type: array + minItems: 2 + maxItems: 2 + items: + type: string + 2DPoint: + description: A 2D Point in the CRS indicated elsewhere + type: array + minItems: 2 + maxItems: 2 + items: + type: number + crs: + title: CRS + oneOf: + - description: Simplification of the object into a url if the other properties are not present + type: string + - type: object + oneOf: + - required: + - uri + properties: + uri: + description: Reference to one coordinate reference system (CRS) + type: string + format: uri + - required: + - wkt + properties: + wkt: + allOf: + - description: An object defining the CRS using the JSON encoding for Well-known text representation of coordinate reference systems 2.0 + - type: object # - $ref: 'projJSON.yaml' + - required: + - referenceSystem + properties: + referenceSystem: + description: A reference system data structure as defined in the MD_ReferenceSystem of the ISO 19115 + type: object processList: type: object required: @@ -1245,12 +2245,6 @@ components: type: array items: $ref: "#/components/schemas/metadata" - jobControlOptions: - type: string - enum: - - sync-execute - - async-execute - - dismiss metadata: oneOf: - allOf: @@ -1271,6 +2265,107 @@ components: oneOf: - type: string - type: object + jobControlOptions: + type: string + enum: + - sync-execute + - async-execute + - dismiss + process: + allOf: + - $ref: "#/components/schemas/processSummary" + - type: object + properties: + inputs: + additionalProperties: + $ref: "#/components/schemas/inputDescription" + outputs: + additionalProperties: + $ref: "#/components/schemas/outputDescription" + inputDescription: + allOf: + - $ref: "#/components/schemas/descriptionType" + - $ref: "#/components/schemas/dataClasses" + - $ref: "#/components/schemas/dataAccessApis" + - $ref: "#/components/schemas/executionUnitRequirements" + - type: object + required: + - schema + properties: + schema: + $ref: "https://json-schema.org/draft/2020-12/schema" + minOccurs: + type: integer + default: 1 + maxOccurs: + oneOf: + - type: integer + default: 1 + - type: string + enum: + - "unbounded" + valuePassing: + type: array + items: + type: string + enum: + - "byValue" + - "byReference" + default: ["byValue", "byReference"] + dataClasses: + type: object + properties: + dataClasses: + type: array + items: + type: string + format: uri + dataAccessApis: + type: object + properties: + dataAccessAPIs: + type: array + items: + $ref: "#/components/schemas/apis" + apis: + description: |- + A non-exhaustive list of OGC and other data access APIs. This list can + be extended as required. + type: string + enum: + - ogc-api-features + - ogc-api-coverages + - ogc-api-edr + - ogc-api-tiles + - ogc-api-moving-features + - ogc-api-sensor-things + - ogc-api-records + - ogc-api-dggs + - stac-api + executionUnitRequirements: + type: object + propreties: + executionUnitRequirements: + type: object + properties: + remote-access: + type: boolean + staging: + type: string + enum: + - local-file + - remote-access + outputDescription: + allOf: + - $ref: "#/components/schemas/descriptionType" + - $ref: "#/components/schemas/dataClasses" + - $ref: "#/components/schemas/dataAccessApis" + - type: object + required: + - schema + properties: + schema: + $ref: "https://json-schema.org/draft/2020-12/schema" execute: type: object properties: @@ -1309,7 +2404,7 @@ components: - $ref: "#/components/schemas/qualifiedValue" - $ref: "#/components/schemas/binaryValue" - $ref: "#/components/schemas/bbox" - # - $ref: "collectionValue.yaml" + # - $ref: "collectionValue" qualifiedValue: allOf: - $ref: "#/components/schemas/format" @@ -1388,6 +2483,91 @@ components: failedUri: type: string format: uri + results: + additionalProperties: + $ref: "#/components/schemas/inlineOrRefValue" + jobList: + type: object + required: + - jobs + - links + properties: + jobs: + type: array + items: + $ref: "#/components/schemas/statusInfo" + links: + type: array + items: + $ref: "#/components/schemas/link" + statusInfo: + allOf: + - $ref: "#/components/schemas/descriptionType" + - type: object + required: + - id + - status + - processingEntityType + properties: + id: + type: string + processID: + type: string + format: uri + processingEntityType: + description: |- + The type of entity that created the job and is doing the processing. + This includes all the data access apis listed in "apis.yaml" plus + the processing APIs of OGC API Processes and OpenEO. + anyOf: + - $ref: "#/components/schemas/apis" + - type: string + enum: + - ogc-api-processes + - openeo + profileEntityType: + description: |- + The type of entity requesting this status information. This may + be differernt than the processing entity. For example, the + processing entity may be OGC API Processes but the status + information is requested via the OpenEO API. + anyOf: + - $ref: "#/components/schemas/apis" + - type: string + enum: + - ogc-api-processes + - openeo + request: + oneOf: + - type: string + - type: object + - $ref: "#/components/schemas/link" + status: + $ref: "#/components/schemas/statusCode" + message: + type: string + exception: + $ref: "#/components/schemas/exception" + created: + type: string + format: date-time + started: + type: string + format: date-time + finished: + type: string + format: date-time + updated: + type: string + format: date-time + progress: + type: integer + minimum: 0 + maximum: 100 + links: + type: array + items: + $ref: "#/components/schemas/link" statusCode: type: string nullable: false diff --git a/ogcapi-services/assets/tms/WebMercartorQuad.json b/ogcapi-services/assets/tms/WebMercartorQuad.json index 451a9e4..5cdccbb 100644 --- a/ogcapi-services/assets/tms/WebMercartorQuad.json +++ b/ogcapi-services/assets/tms/WebMercartorQuad.json @@ -1,262 +1,363 @@ { - "id": "WebMercatorQuad", - "title": "Google Maps Compatible for the World", - "uri": "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad", - "crs": "http://www.opengis.net/def/crs/EPSG/0/3857", - "orderedAxes": ["X", "Y"], - "wellKnownScaleSet": "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible", - "tileMatrices": - [ - { - "id": "0", - "scaleDenominator": 559082264.028717, - "cellSize": 156543.033928041, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 1, - "matrixHeight": 1 - }, - { - "id": "1", - "scaleDenominator": 279541132.014358, - "cellSize": 78271.5169640204, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 2, - "matrixHeight": 2 - }, - { - "id": "2", - "scaleDenominator": 139770566.007179, - "cellSize": 39135.7584820102, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 4, - "matrixHeight": 4 - }, - { - "id": "3", - "scaleDenominator": 69885283.0035897, - "cellSize": 19567.8792410051, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 8, - "matrixHeight": 8 - }, - { - "id": "4", - "scaleDenominator": 34942641.5017948, - "cellSize": 9783.93962050256, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 16, - "matrixHeight": 16 - }, - { - "id": "5", - "scaleDenominator": 17471320.7508974, - "cellSize": 4891.96981025128, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 32, - "matrixHeight": 32 - }, - { - "id": "6", - "scaleDenominator": 8735660.37544871, - "cellSize": 2445.98490512564, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 64, - "matrixHeight": 64 - }, - { - "id": "7", - "scaleDenominator": 4367830.18772435, - "cellSize": 1222.99245256282, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 128, - "matrixHeight": 128 - }, - { - "id": "8", - "scaleDenominator": 2183915.09386217, - "cellSize": 611.49622628141, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 256, - "matrixHeight": 256 - }, - { - "id": "9", - "scaleDenominator": 1091957.54693108, - "cellSize": 305.748113140704, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 512, - "matrixHeight": 512 - }, - { - "id": "10", - "scaleDenominator": 545978.773465544, - "cellSize": 152.874056570352, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 1024, - "matrixHeight": 1024 - }, - { - "id": "11", - "scaleDenominator": 272989.386732772, - "cellSize": 76.4370282851762, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 2048, - "matrixHeight": 2048 - }, - { - "id": "12", - "scaleDenominator": 136494.693366386, - "cellSize": 38.2185141425881, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 4096, - "matrixHeight": 4096 - }, - { - "id": "13", - "scaleDenominator": 68247.346683193, - "cellSize": 19.109257071294, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 8192, - "matrixHeight": 8192 - }, - { - "id": "14", - "scaleDenominator": 34123.6733415964, - "cellSize": 9.55462853564703, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 16384, - "matrixHeight": 16384 - }, - { - "id": "15", - "scaleDenominator": 17061.8366707982, - "cellSize": 4.77731426782351, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 32768, - "matrixHeight": 32768 - }, - { - "id": "16", - "scaleDenominator": 8530.91833539913, - "cellSize": 2.38865713391175, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 65536, - "matrixHeight": 65536 - }, - { - "id": "17", - "scaleDenominator": 4265.45916769956, - "cellSize": 1.19432856695587, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 131072, - "matrixHeight": 131072 - }, - { - "id": "18", - "scaleDenominator": 2132.72958384978, - "cellSize": 0.597164283477939, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 262144, - "matrixHeight": 262144 - }, - { - "id": "19", - "scaleDenominator": 1066.36479192489, - "cellSize": 0.29858214173897, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 524288, - "matrixHeight": 524288 - }, - { - "id": "20", - "scaleDenominator": 533.182395962445, - "cellSize": 0.149291070869485, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 1048576, - "matrixHeight": 1048576 - }, - { - "id": "21", - "scaleDenominator": 266.591197981222, - "cellSize": 0.0746455354347424, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 2097152, - "matrixHeight": 2097152 - }, - { - "id": "22", - "scaleDenominator": 133.295598990611, - "cellSize": 0.0373227677173712, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 4194304, - "matrixHeight": 4194304 - }, - { - "id": "23", - "scaleDenominator": 66.6477994953056, - "cellSize": 0.0186613838586856, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 8388608, - "matrixHeight": 8388608 - }, - { - "id": "24", - "scaleDenominator": 33.3238997476528, - "cellSize": 0.0093306919293428, - "pointOfOrigin": [-20037508.3427892,20037508.3427892], - "tileWidth": 256, - "tileHeight": 256, - "matrixWidth": 16777216, - "matrixHeight": 16777216 - } - ] - } - \ No newline at end of file + "id": "WebMercatorQuad", + "title": "Google Maps Compatible for the World", + "uri": "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad", + "crs": "http://www.opengis.net/def/crs/EPSG/0/3857", + "orderedAxes": [ + "X", + "Y" + ], + "wellKnownScaleSet": "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible", + "tileMatrices": [ + { + "id": "0", + "scaleDenominator": 559082264.028717, + "cellSize": 156543.033928041, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 1, + "matrixHeight": 1 + }, + { + "id": "1", + "scaleDenominator": 279541132.014358, + "cellSize": 78271.5169640204, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 2, + "matrixHeight": 2 + }, + { + "id": "2", + "scaleDenominator": 139770566.007179, + "cellSize": 39135.7584820102, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 4, + "matrixHeight": 4 + }, + { + "id": "3", + "scaleDenominator": 69885283.0035897, + "cellSize": 19567.8792410051, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 8, + "matrixHeight": 8 + }, + { + "id": "4", + "scaleDenominator": 34942641.5017948, + "cellSize": 9783.93962050256, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 16, + "matrixHeight": 16 + }, + { + "id": "5", + "scaleDenominator": 17471320.7508974, + "cellSize": 4891.96981025128, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 32, + "matrixHeight": 32 + }, + { + "id": "6", + "scaleDenominator": 8735660.37544871, + "cellSize": 2445.98490512564, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 64, + "matrixHeight": 64 + }, + { + "id": "7", + "scaleDenominator": 4367830.18772435, + "cellSize": 1222.99245256282, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 128, + "matrixHeight": 128 + }, + { + "id": "8", + "scaleDenominator": 2183915.09386217, + "cellSize": 611.49622628141, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 256, + "matrixHeight": 256 + }, + { + "id": "9", + "scaleDenominator": 1091957.54693108, + "cellSize": 305.748113140704, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 512, + "matrixHeight": 512 + }, + { + "id": "10", + "scaleDenominator": 545978.773465544, + "cellSize": 152.874056570352, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 1024, + "matrixHeight": 1024 + }, + { + "id": "11", + "scaleDenominator": 272989.386732772, + "cellSize": 76.4370282851762, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 2048, + "matrixHeight": 2048 + }, + { + "id": "12", + "scaleDenominator": 136494.693366386, + "cellSize": 38.2185141425881, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 4096, + "matrixHeight": 4096 + }, + { + "id": "13", + "scaleDenominator": 68247.346683193, + "cellSize": 19.109257071294, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 8192, + "matrixHeight": 8192 + }, + { + "id": "14", + "scaleDenominator": 34123.6733415964, + "cellSize": 9.55462853564703, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 16384, + "matrixHeight": 16384 + }, + { + "id": "15", + "scaleDenominator": 17061.8366707982, + "cellSize": 4.77731426782351, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 32768, + "matrixHeight": 32768 + }, + { + "id": "16", + "scaleDenominator": 8530.91833539913, + "cellSize": 2.38865713391175, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 65536, + "matrixHeight": 65536 + }, + { + "id": "17", + "scaleDenominator": 4265.45916769956, + "cellSize": 1.19432856695587, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 131072, + "matrixHeight": 131072 + }, + { + "id": "18", + "scaleDenominator": 2132.72958384978, + "cellSize": 0.597164283477939, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 262144, + "matrixHeight": 262144 + }, + { + "id": "19", + "scaleDenominator": 1066.36479192489, + "cellSize": 0.29858214173897, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 524288, + "matrixHeight": 524288 + }, + { + "id": "20", + "scaleDenominator": 533.182395962445, + "cellSize": 0.149291070869485, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 1048576, + "matrixHeight": 1048576 + }, + { + "id": "21", + "scaleDenominator": 266.591197981222, + "cellSize": 0.0746455354347424, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 2097152, + "matrixHeight": 2097152 + }, + { + "id": "22", + "scaleDenominator": 133.295598990611, + "cellSize": 0.0373227677173712, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 4194304, + "matrixHeight": 4194304 + }, + { + "id": "23", + "scaleDenominator": 66.6477994953056, + "cellSize": 0.0186613838586856, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 8388608, + "matrixHeight": 8388608 + }, + { + "id": "24", + "scaleDenominator": 33.3238997476528, + "cellSize": 0.0093306919293428, + "cornerOfOrigin": "topLeft", + "pointOfOrigin": [ + -20037508.3427892, + 20037508.3427892 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 16777216, + "matrixHeight": 16777216 + } + ] +} \ No newline at end of file diff --git a/ogcapi-services/src/openapi.rs b/ogcapi-services/src/openapi.rs index 3c236a7..9bd3ea7 100644 --- a/ogcapi-services/src/openapi.rs +++ b/ogcapi-services/src/openapi.rs @@ -1,15 +1,8 @@ use utoipa::OpenApi; /// TODO: remove once Open API 3.1 is supported -#[cfg(all( - any(feature = "common", feature = "features", feature = "processes"), - not(feature = "edr") -))] -pub(crate) static OPENAPI: &[u8; 45786] = include_bytes!("../assets/openapi/openapi.yaml"); - -/// TODO: remove once Open API 3.1 is supported -#[cfg(feature = "edr")] -pub(crate) static OPENAPI: &[u8; 122244] = include_bytes!("../assets/openapi/openapi-edr.yaml"); +pub(crate) static OPENAPI: &[u8; 87178] = include_bytes!("../assets/openapi/openapi.yaml"); +// pub(crate) static OPENAPI: &[u8; 122244] = include_bytes!("../assets/openapi/openapi-edr.yaml"); #[derive(Default, Clone)] pub(crate) struct OpenAPI(pub(crate) openapiv3::OpenAPI); diff --git a/ogcapi-services/src/routes/collections.rs b/ogcapi-services/src/routes/collections.rs index eb41438..af86b67 100755 --- a/ogcapi-services/src/routes/collections.rs +++ b/ogcapi-services/src/routes/collections.rs @@ -6,6 +6,8 @@ use axum::{ use hyper::HeaderMap; use utoipa_axum::{router::OpenApiRouter, routes}; +#[cfg(feature = "tiles")] +use ogcapi_types::common::link_rel::TILESETS_VECTOR; use ogcapi_types::common::{ Collection, Collections, Crs, Exception, Link, Linked, Query, link_rel::{DATA, ITEMS, ROOT, SELF}, @@ -17,10 +19,10 @@ use crate::{ extractors::{Qs, RemoteUrl}, }; -const CONFORMANCE: [&str; 4] = [ +const CONFORMANCE: [&str; 5] = [ "http://www.opengis.net/spec/ogcapi-common-1/1.0/conf/core", "http://www.opengis.net/spec/ogcapi-common-1/1.0/conf/landingPage", - // "http://www.opengis.net/spec/ogcapi-common-1/1.0/conf/oas30", + "http://www.opengis.net/spec/ogcapi-common-1/1.0/conf/oas30", // "http://www.opengis.net/spec/ogcapi-common-1/1.0/conf/html", "http://www.opengis.net/spec/ogcapi_common-2/1.0/conf/json", "http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/collections", @@ -139,19 +141,24 @@ async fn read( ]); #[cfg(not(feature = "stac"))] - collection.links.insert_or_update(&[Link::new( - &url.join(&format!("{}/items", collection.id))?, - ITEMS, - ) - .mediatype(GEO_JSON)]); + { + let items_url = url.join(&format!("{}/items", collection.id))?; + let items_link = Link::new(items_url, ITEMS).mediatype(GEO_JSON); + collection.links.insert_or_update(&[items_link]); + } + + #[cfg(feature = "tiles")] + { + let tiles_url = url.join(&format!("{}/tiles", collection.id))?; + let tiles_link = Link::new(tiles_url, TILESETS_VECTOR).mediatype(JSON); + collection.links.insert_or_update(&[tiles_link]); + } #[cfg(feature = "stac")] if collection.r#type == "Collection" { - collection.links.insert_or_update(&[Link::new( - url.join(&format!("{}/items", collection.id))?, - ITEMS, - ) - .mediatype(GEO_JSON)]); + let items_url = url.join(&format!("{}/items", collection.id))?; + let items_link = Link::new(items_url, ITEMS).mediatype(GEO_JSON); + collection.links.insert_or_update(&[items_link]); } collection.links.resolve_relative_links(); @@ -278,6 +285,12 @@ async fn collections( ITEMS, ) .mediatype(GEO_JSON), + #[cfg(feature = "tiles")] + Link::new( + url.join(&format!("collections/{}/tiles", collection.id))?, + TILESETS_VECTOR, + ) + .mediatype(JSON), ]); collection.links.resolve_relative_links() diff --git a/ogcapi-services/src/routes/common.rs b/ogcapi-services/src/routes/common.rs index 1d73df7..38bb258 100644 --- a/ogcapi-services/src/routes/common.rs +++ b/ogcapi-services/src/routes/common.rs @@ -4,6 +4,8 @@ use utoipa_axum::{router::OpenApiRouter, routes}; #[cfg(feature = "stac")] use ogcapi_types::common::link_rel::SEARCH; +#[cfg(feature = "tiles")] +use ogcapi_types::common::link_rel::{TILESETS_VECTOR, TILING_SCHEMES}; use ogcapi_types::common::{ Conformance, Exception, LandingPage, Link, Linked, link_rel::{CONFORMANCE, ROOT, SELF, SERVICE_DESC, SERVICE_DOC}, @@ -66,6 +68,14 @@ pub async fn root( Link::new("search", SEARCH) .title("URI for the STAC API - Item Search endpoint") .mediatype(JSON), + #[cfg(feature = "tiles")] + Link::new("tileMatrixSets", TILING_SCHEMES) + .title("List of tileMatrixSets implemented by this API in JSON") + .mediatype(JSON), + #[cfg(feature = "tiles")] + Link::new("tiles", TILESETS_VECTOR) + .title("List of available vector tilesets for the dataset") + .mediatype(JSON), ]); root.links.resolve_relative_links(); diff --git a/ogcapi-services/src/routes/tiles.rs b/ogcapi-services/src/routes/tiles.rs index 014dc39..0ef951b 100644 --- a/ogcapi-services/src/routes/tiles.rs +++ b/ogcapi-services/src/routes/tiles.rs @@ -1,28 +1,27 @@ -use std::{collections::HashMap, sync::OnceLock}; +use std::sync::{Arc, OnceLock}; use axum::{ Json, extract::{Path, State}, - http::StatusCode, + http::HeaderMap, }; -use serde::{Deserialize, Serialize}; -use utoipa::IntoParams; +use dashmap::DashMap; use utoipa_axum::{router::OpenApiRouter, routes}; use ogcapi_types::{ common::{ - Crs, Exception, Link, - link_rel::{TILESETS_VECTOR, TILING_SCHEME}, - media_type::JSON, + Exception, Link, + link_rel::{ITEM, SELF, TILESETS_VECTOR, TILING_SCHEME}, + media_type::{JSON, MVT}, }, tiles::{ - DataType, TileMatrix, TileMatrixSet, TileMatrixSetId, TileMatrixSetItem, TileMatrixSets, - TileQuery, TileSet, TileSets, TilesCrs, + CollectionTileParams, DataType, TileMatrixSet, TileMatrixSetId, TileMatrixSetItem, + TileMatrixSets, TileParams, TileQuery, TileSet, TileSetItem, TileSets, }, }; use crate::{ - AppState, Error, Result, + AppState, Result, extractors::{Qs, RemoteUrl}, }; @@ -41,10 +40,9 @@ const CONFORMANCE: [&str; 7] = [ // "http://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/netcdf", ]; -const WEB_MERCARTOR_QUAD: &[u8; 8005] = include_bytes!("../../assets/tms/WebMercartorQuad.json"); +const WEB_MERCARTOR_QUAD: &[u8; 8744] = include_bytes!("../../assets/tms/WebMercartorQuad.json"); -static TMS: OnceLock> = OnceLock::new(); -static TM: OnceLock>> = OnceLock::new(); +static TMS: OnceLock>> = OnceLock::new(); /// Retrieve the list of available tiling schemes (tile matrix sets) #[utoipa::path(get, path = "/tileMatrixSets", tag = "Tiling Schemes", @@ -61,26 +59,22 @@ static TM: OnceLock>> = Onc ) )] async fn tile_matrix_sets(RemoteUrl(url): RemoteUrl) -> Result> { - let tms = TMS.get().expect("TMS cell to be inizialized"); - - let mut tile_matrix_sets = Vec::new(); - - for tms in tms.values() { - let item = TileMatrixSetItem { - id: Some(tms.id.to_owned()), - title: tms.title.to_owned(), - links: vec![Link::new( - url.join(&format!( - "tileMatrixSets/{}", - serde_json::to_string(&tms.id).unwrap() - ))?, - TILING_SCHEME, - )], - ..Default::default() - }; + let registry = TMS.get().expect("TMS cell to be inizialized"); - tile_matrix_sets.push(item); - } + let tile_matrix_sets = registry + .iter() + .map(|tms| { + let path = format!("tileMatrixSets/{}", &tms.id); + let url = url.join(&path).expect("failed to parse url"); + let link = Link::new(url, TILING_SCHEME); + TileMatrixSetItem { + id: Some(tms.id.to_owned()), + title: tms.title.to_owned(), + links: vec![link], + ..Default::default() + } + }) + .collect(); Ok(Json(TileMatrixSets { tile_matrix_sets })) } @@ -111,11 +105,30 @@ async fn tile_matrix_sets(RemoteUrl(url): RemoteUrl) -> Result) -> Result> { - match TMS.get().and_then(|tms| tms.get(&id)) { - Some(tms) => Ok(Json(tms.to_owned())), - None => Err(Error::ApiException( - (StatusCode::NOT_FOUND, "Unable to find resource".to_string()).into(), - )), + let registry = TMS.get().expect("TMS cell to be inizialized"); + + if let Some(tms) = registry.get(&id) { + Ok(Json(tms.to_owned())) + } else { + // fetch online + let url = format!( + "https://raw.githubusercontent.com/opengeospatial/2D-Tile-Matrix-Set/master/registry/json/{id}.json" + ); + match reqwest::get(url).await { + Ok(r) => match r.json::().await { + Ok(tms) => { + // registry.insert(tms.id.to_owned(), tms); + Ok(Json(tms.to_owned())) + } + Err(e) => Err(Exception::new_from_status(500).detail(e.to_string()).into()), + }, + Err(e) => { + let status = e.status().map(|s| s.as_u16()).unwrap_or(500); + Err(Exception::new_from_status(status) + .detail(e.to_string()) + .into()) + } + } } } @@ -133,9 +146,40 @@ async fn tile_matrix_set(Path(id): Path) -> Result Result> { +async fn tiles(RemoteUrl(url): RemoteUrl) -> Result> { + let registry = TMS.get().expect("TMS cell to be inizialized"); + + let mut tilesets = Vec::new(); + for tms in registry.iter() { + let tms_path = format!("tileMatrixSets/{}", tms.id); + let tms_url = url.join(&tms_path).expect("failed to parse url"); + let tms_link = Link::new(tms_url, TILING_SCHEME) + .title("Tiling scheme definition") + .mediatype(JSON); + + let self_path = format!("tiles/{}", tms.id); + let self_url = url.join(&self_path).expect("failed to parse url"); + let self_link = Link::new(self_url, SELF) + .title("Tileset definition") + .mediatype(JSON); + + let tiles_path = format!("tiles/{}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}", tms.id); + let tiles_url = url.join(&tiles_path).expect("failed to parse url"); + let tiles_link = Link::new(tiles_url, ITEM).mediatype(MVT).templated(true); + + let tileset = TileSetItem { + title: Some(format!("Whole dataset in {}", tms.id)), + data_type: DataType::Vector, + crs: tms.crs.to_owned(), + tile_matrix_set_uri: None, + links: vec![self_link, tiles_link, tms_link], + }; + + tilesets.push(tileset); + } + let tile_sets = TileSets { - tilesets: vec![], + tilesets, links: vec![], }; @@ -149,8 +193,7 @@ async fn tiles() -> Result> { ( "tileMatrixSetId" = TileMatrixSetId, Path, description = "Identifier for a supported TileMatrixSet" - ), - TileQuery, + ) ), responses( ( @@ -164,17 +207,53 @@ async fn tiles() -> Result> { ) ) )] -async fn tile_set() -> Result> { +async fn tiles_tile_set( + RemoteUrl(url): RemoteUrl, + Path(tms_id): Path, +) -> Result<(HeaderMap, Json)> { + let mut headers = HeaderMap::new(); + + // tms + let registry = TMS.get().expect("TMS cell to be inizialized"); + + let Some(tms) = registry.get(&tms_id) else { + return Err(Exception::new_from_status(404) + .detail(format!("Tile matrix set `{tms_id}` not found")) + .into()); + }; + + let tms_path = format!("../tileMatrixSets/{tms_id}"); + let tms_url = url.join(&tms_path).expect("failed to parse url"); + + // links + let self_link = Link::new(url.clone(), SELF).mediatype(JSON); + + let tms_link = Link::new(tms_url.clone(), TILING_SCHEME).mediatype(JSON); + + let tiles_path = format!("{tms_id}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}"); + let tiles_url = url.join(&tiles_path).expect("failed to parse url"); + headers.insert( + "Link-Template", + format!( + "<{}>; rel=\"{ITEM}\"; type=\"{MVT}\"; var-base=\"./vars/\"", + tiles_url + ) + .parse() + .unwrap(), + ); + let tiles_link = Link::new(tiles_url, ITEM).mediatype(MVT).templated(true); + + // tileset let tile_set = TileSet { - title: Default::default(), + title: Some(tms_id.to_string()), description: Default::default(), keywords: Default::default(), data_type: DataType::Vector, - tile_matrix_set_uri: Default::default(), + tile_matrix_set_uri: tms.uri.to_owned(), tile_matrix_set_limits: Default::default(), - crs: TilesCrs::Simple(Crs::default2d().to_string()), + crs: tms.crs.to_owned(), epoch: Default::default(), - links: Default::default(), + links: vec![self_link, tms_link, tiles_link], layers: Default::default(), bounding_box: Default::default(), style: Default::default(), @@ -189,7 +268,7 @@ async fn tile_set() -> Result> { media_types: Default::default(), }; - Ok(Json(tile_set)) + Ok((headers, Json(tile_set))) } /// Retrieve a vector tile including one or more collections from the dataset. @@ -217,17 +296,40 @@ async fn tiles_tile( Qs(query): Qs, State(state): State, ) -> Result> { - let tms = TMS - .get() - .and_then(|tms| tms.get(¶ms.tile_matrix_set_id)) - .expect("Get tms from TMS"); + // tile matrix set + let tms_id = ¶ms.tile_matrix_set_id; + let Some(tms) = TMS.get().and_then(|tms| tms.get(tms_id)) else { + return Err(Exception::new_from_status(404) + .detail(format!("Tile matrix set `{tms_id}` not found",)) + .into()); + }; + + // tile matrix + let tm_id = ¶ms.tile_matrix; + let Some(tm) = tms.tile_matrices.iter().find(|tm| tm.id.as_str() == tm_id) else { + return Err(Exception::new_from_status(404) + .detail(format!( + "No tile matrix with id `{tm_id}` in tile matrix set `{tms_id}`" + )) + .into()); + }; + + // check bounds + let row = params.tile_row; + let col = params.tile_col; + + if row >= tm.matrix_height.get() as u32 || col >= tm.matrix_width.get() as u32 { + return Err(Exception::new_from_status(404) + .detail(format!("Tile row/col `{row}/{col}` out of bounds")) + .into()); + } let tiles = state .drivers .tiles .tile( &query.collections, - tms, + &tms, ¶ms.tile_matrix, params.tile_row, params.tile_col, @@ -237,20 +339,154 @@ async fn tiles_tile( Ok(tiles) } -#[derive(Serialize, Deserialize, IntoParams, Debug)] -#[serde(rename_all = "camelCase")] -pub struct TileParams { - /// Identifier selecting one of the TileMatrixSetId supported by the resource. - tile_matrix_set_id: TileMatrixSetId, - /// Identifier selecting one of the scales defined in the TileMatrixSet - /// and representing the scaleDenominator the tile. - tile_matrix: String, - /// Row index of the tile on the selected TileMatrix. It cannot exceed - /// the MatrixWidth-1 for the selected TileMatrix. - tile_row: u32, - /// Column index of the tile on the selected TileMatrix. It cannot exceed - /// the MatrixHeight-1 for the selected TileMatrix. - tile_col: u32, +/// Retrieve a list of available vector tilesets for the collection +#[utoipa::path(get, path = "/collections/{collectionId}/tiles", tag = "Vector Tiles", + params( + ("collectionId" = String, Path, description = "local identifier of a collection") + ), + responses( + ( + status = 200, + description = "List of available tilesets.", + body = TileSets + ), + ( + status = 500, description = "A server error occurred.", + body = Exception, example = json!(Exception::new_from_status(500)) + ) + ) +)] +async fn collection_tiles( + RemoteUrl(url): RemoteUrl, + Path(collection_id): Path, +) -> Result> { + let registry = TMS.get().expect("TMS cell to be inizialized"); + + let mut tilesets = Vec::new(); + for tms in registry.iter() { + let tms_path = format!("../../tileMatrixSets/{}", tms.id); + let tms_url = url.join(&tms_path).expect("failed to parse url"); + let tms_link = Link::new(tms_url, TILING_SCHEME) + .title("Tiling scheme definition") + .mediatype(JSON); + + let tileset_path = format!("tiles/{}", tms.id); + let tileset_url = url.join(&tileset_path).expect("failed to parse url"); + let tileset_link = Link::new(tileset_url, SELF) + .title("Tileset definition") + .mediatype(JSON); + + let tiles_path = format!("tiles/{}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}", tms.id); + let tiles_url = url.join(&tiles_path).expect("failed to parse url"); + let tiles_link = Link::new(tiles_url, ITEM).mediatype(MVT).templated(true); + + let tileset = TileSetItem { + title: Some(collection_id.to_owned()), + data_type: DataType::Vector, + crs: tms.crs.to_owned(), + tile_matrix_set_uri: None, + links: vec![tileset_link, tiles_link, tms_link], + }; + + tilesets.push(tileset); + } + + let tile_sets = TileSets { + tilesets, + links: vec![], + }; + + Ok(Json(tile_sets)) +} + +/// Retrieve the vector tileset metadata for a specific collection and the +/// specified tiling scheme (tile matrix set) +#[utoipa::path(get, path = "/collections/{collectionId}/tiles/{tileMatrixSetId}", tag = "Vector Tiles", + params( + ("collectionId" = String, Path, description = "local identifier of a collection"), + ( + "tileMatrixSetId" = TileMatrixSetId, Path, + description = "Identifier for a supported TileMatrixSet" + ), + ), + responses( + ( + status = 200, + description = "Description of the tileset", + body = TileSet + ), + ( + status = 500, description = "A server error occurred.", + body = Exception, example = json!(Exception::new_from_status(500)) + ) + ) +)] +async fn collection_tile_set( + State(_state): State, + RemoteUrl(url): RemoteUrl, + Path((collection_id, tms_id)): Path<(String, TileMatrixSetId)>, +) -> Result<(HeaderMap, Json)> { + let mut headers = HeaderMap::new(); + + // tms + let registry = TMS.get().expect("TMS cell to be inizialized"); + + let Some(tms) = registry.get(&tms_id) else { + return Err(Exception::new_from_status(404) + .detail(format!("Tile matrix set `{tms_id}` not found")) + .into()); + }; + + let tms_path = format!("../../../tileMatrixSets/{tms_id}"); + let tms_url = url.join(&tms_path).expect("failed to parse url"); + + // links + let self_url = url.join(&tms_id.to_string()).expect("failed to parse url"); + let self_link = Link::new(self_url, SELF).mediatype(JSON); + + let tms_link = Link::new(tms_url.clone(), TILING_SCHEME).mediatype(JSON); + + let tiles_path = format!( + "/collections/{collection_id}/tiles/{tms_id}/{{tileMatrix}}/{{tileRow}}/{{tileCol}}" + ); + let tiles_url = url.join(&tiles_path).expect("failed to parse url"); + headers.insert( + "Link-Template", + format!( + "<{}>; rel=\"{ITEM}\"; type=\"{MVT}\"; var-base=\"./vars/\"", + tiles_url + ) + .parse() + .unwrap(), + ); + let tiles_link = Link::new(tiles_url, ITEM).mediatype(MVT).templated(true); + + // tileset + let tile_set = TileSet { + title: Some(collection_id.to_string()), + description: Default::default(), + keywords: Default::default(), + data_type: DataType::Vector, + tile_matrix_set_uri: tms.uri.to_owned(), + tile_matrix_set_limits: Default::default(), + crs: tms.crs.to_owned(), + epoch: Default::default(), + links: vec![self_link, tms_link, tiles_link], + layers: Default::default(), + bounding_box: Default::default(), + style: Default::default(), + center_point: Default::default(), + attribution: Default::default(), + license: Default::default(), + access_constraints: Default::default(), + version: Default::default(), + created: Default::default(), + updated: Default::default(), + point_of_contact: Default::default(), + media_types: Default::default(), + }; + + Ok((headers, Json(tile_set))) } /// Retrieve a vector tile from a collection. @@ -258,10 +494,6 @@ pub struct TileParams { path = "/collections/{collectionId}/tiles/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol}", tag = "Vector Tiles", params( - ( - "collectionId" = String, Path, - description = "Local identifier of a vector tile collection" - ), TileParams, TileQuery, ), @@ -278,23 +510,45 @@ pub struct TileParams { ) )] async fn collection_tile( - Path(collection_id): Path, - Path(params): Path, + Path(params): Path, Qs(mut query): Qs, State(state): State, ) -> Result> { - let tms = TMS - .get() - .and_then(|tms| tms.get(¶ms.tile_matrix_set_id)) - .expect("Get tms from TMS"); - - let collections = if !query.collections.is_empty() { - if !query.collections.contains(&collection_id) { - query.collections.push(collection_id); + // tile matrix set + let tms_id = ¶ms.tile_params.tile_matrix_set_id; + let Some(tms) = TMS.get().and_then(|tms| tms.get(tms_id)) else { + return Err(Exception::new_from_status(404) + .detail(format!("Tile matrix set `{tms_id}` not found",)) + .into()); + }; + + // tile matrix + let tm_id = ¶ms.tile_params.tile_matrix; + let Some(tm) = tms.tile_matrices.iter().find(|tm| tm.id.as_str() == tm_id) else { + return Err(Exception::new_from_status(404) + .detail(format!( + "No tile matrix with id `{tm_id}` in tile matrix set `{tms_id}`" + )) + .into()); + }; + + // check bounds + let row = params.tile_params.tile_row; + let col = params.tile_params.tile_col; + + if row >= tm.matrix_height.get() as u32 || col >= tm.matrix_width.get() as u32 { + return Err(Exception::new_from_status(404) + .detail(format!("Tile row/col `{row}/{col}` out of bounds")) + .into()); + } + + let collections = if query.collections.is_empty() { + vec![params.collection_id] + } else { + if !query.collections.contains(¶ms.collection_id) { + query.collections.push(params.collection_id); } query.collections - } else { - vec![collection_id] }; let tiles = state @@ -302,10 +556,10 @@ async fn collection_tile( .tiles .tile( &collections, - tms, - ¶ms.tile_matrix, - params.tile_row, - params.tile_col, + &tms, + ¶ms.tile_params.tile_matrix, + params.tile_params.tile_row, + params.tile_params.tile_col, ) .await?; @@ -314,38 +568,26 @@ async fn collection_tile( pub(crate) fn router(state: &AppState) -> OpenApiRouter { let mut root = state.root.write().unwrap(); - root.links.push( - Link::new("tiles", TILESETS_VECTOR) - .title("List of available vector features tilesets for the dataset") - .mediatype(JSON), - ); + root.links.extend([Link::new("tiles", TILESETS_VECTOR) + .title("List of available vector features tilesets for the dataset") + .mediatype(JSON)]); state.conformance.write().unwrap().extend(&CONFORMANCE); // Setup tile matrix sets - let mut tms_map = HashMap::new(); + let tms_map = DashMap::new(); let web_mercartor_quad: TileMatrixSet = serde_json::from_slice(WEB_MERCARTOR_QUAD).expect("parse tms"); tms_map.insert(web_mercartor_quad.id.to_owned(), web_mercartor_quad); - - let mut tm = HashMap::new(); - for tms in tms_map.values() { - tm.insert(tms.id.to_owned(), HashMap::new()); - for tile_matrix in &tms.tile_matrices { - tm.get_mut(&tms.id).and_then(|tm_map| { - tm_map.insert(tile_matrix.id.to_owned(), tile_matrix.to_owned()) - }); - } - } - TMS.set(tms_map).expect("set `TMS` once cell content"); - TM.set(tm).expect("set `TM` once cell content"); + TMS.set(Arc::new(tms_map)).expect("set `TMS` content"); OpenApiRouter::new() .routes(routes!(tile_matrix_sets)) .routes(routes!(tile_matrix_set)) .routes(routes!(tiles)) - .routes(routes!(tile_set)) + .routes(routes!(tiles_tile_set)) .routes(routes!(tiles_tile)) - // .route("/collections/{collection_id}/tiles", get(tiles)) + .routes(routes!(collection_tiles)) + .routes(routes!(collection_tile_set)) .routes(routes!(collection_tile)) } diff --git a/ogcapi-types/src/common/collection.rs b/ogcapi-types/src/common/collection.rs index a8b9ac2..4006cb8 100644 --- a/ogcapi-types/src/common/collection.rs +++ b/ogcapi-types/src/common/collection.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; -use serde_with::DisplayFromStr; use utoipa::ToSchema; use super::{Crs, Extent, Link}; @@ -8,8 +7,6 @@ use super::{Crs, Extent, Link}; // const CRS_REF: &str = "#/crs"; /// A body of resources that belong or are used together. An aggregate, set, or group of related resources. -#[serde_with::serde_as] -#[serde_with::skip_serializing_none] #[derive(Serialize, Deserialize, ToSchema, Debug, PartialEq, Clone)] #[serde(rename_all = "camelCase")] pub struct Collection { @@ -18,31 +15,34 @@ pub struct Collection { #[cfg(feature = "stac")] #[serde(default = "collection")] pub r#type: String, + #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub keywords: Vec, /// Attribution for the collection. + #[serde(skip_serializing_if = "Option::is_none")] pub attribution: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub extent: Option, /// An indicator about the type of the items in the collection. #[serde(default = "feature")] pub item_type: String, /// The list of coordinate reference systems supported by the API; the first item is the default coordinate reference system. #[serde(default)] - #[serde_as(as = "Vec")] #[schema(value_type = Vec)] pub crs: Vec, - #[serde(default)] - #[serde_as(as = "Option")] + #[serde(default, skip_serializing_if = "Option::is_none")] #[schema(value_type = String)] pub storage_crs: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub storage_crs_coordinate_epoch: Option, #[serde(default)] pub links: Vec, /// Detailed information relevant to individual query types #[cfg(feature = "edr")] - #[serde(rename = "data_queries")] + #[serde(rename = "data_queries", skip_serializing_if = "Option::is_none")] pub data_queries: Option, /// List of formats the results can be presented in #[cfg(feature = "edr")] diff --git a/ogcapi-types/src/common/collections.rs b/ogcapi-types/src/common/collections.rs index 5a46fac..d8cda69 100644 --- a/ogcapi-types/src/common/collections.rs +++ b/ogcapi-types/src/common/collections.rs @@ -1,24 +1,23 @@ use chrono::{SecondsFormat, Utc}; use serde::{Deserialize, Serialize}; -use serde_with::DisplayFromStr; use utoipa::ToSchema; use super::{Collection, Crs, Link}; -#[serde_with::serde_as] -#[serde_with::skip_serializing_none] #[derive(Serialize, Deserialize, ToSchema, Debug, PartialEq, Clone)] #[serde(rename_all = "camelCase")] pub struct Collections { #[serde(default)] pub links: Vec, + #[serde(skip_serializing_if = "Option::is_none")] pub time_stamp: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub number_matched: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub number_returned: Option, pub collections: Vec, #[serde(default)] - #[serde_as(as = "Vec")] - #[schema(value_type = String)] + #[schema(value_type = Vec)] pub crs: Vec, } diff --git a/ogcapi-types/src/common/crs.rs b/ogcapi-types/src/common/crs.rs index cb91384..3470157 100644 --- a/ogcapi-types/src/common/crs.rs +++ b/ogcapi-types/src/common/crs.rs @@ -1,6 +1,9 @@ -use std::{fmt, str}; +use std::{ + fmt, + str::{self, FromStr}, +}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::Visitor}; /// Default CRS for coordinates without height pub const OGC_CRS84: &str = "http://www.opengis.net/def/crs/OGC/1.3/CRS84"; @@ -9,7 +12,7 @@ pub const OGC_CRS84: &str = "http://www.opengis.net/def/crs/OGC/1.3/CRS84"; pub const OGC_CRS84H: &str = "http://www.opengis.net/def/crs/OGC/0/CRS84h"; /// Coordinate Reference System (CRS) -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Crs { pub authority: Authority, pub version: String, @@ -126,6 +129,44 @@ impl str::FromStr for Crs { } } +struct StrVisitior; + +impl<'de> Visitor<'de> for StrVisitior { + type Value = Crs; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a crs string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match Crs::from_str(v) { + Ok(crs) => Ok(crs), + Err(e) => Err(E::custom(e)), + } + } +} + +impl<'de> Deserialize<'de> for Crs { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_string(StrVisitior) + } +} + +impl Serialize for Crs { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + /// CRS Authorities #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash)] pub enum Authority { @@ -142,7 +183,7 @@ impl fmt::Display for Authority { } } -impl str::FromStr for Authority { +impl FromStr for Authority { type Err = &'static str; fn from_str(s: &str) -> Result { @@ -166,6 +207,14 @@ mod tests { assert_eq!(format!("{crs:#}"), OGC_CRS84) } + #[test] + fn serde_crs() { + let original = format!("\"{OGC_CRS84}\""); + let crs: Crs = serde_json::from_str(&original).unwrap(); + let crs_str = serde_json::to_string(&crs).unwrap(); + assert_eq!(format!("{crs_str}"), original) + } + #[test] fn from_epsg() { let code = 4979; diff --git a/ogcapi-types/src/common/extent.rs b/ogcapi-types/src/common/extent.rs index ab011c4..d9fdbd7 100644 --- a/ogcapi-types/src/common/extent.rs +++ b/ogcapi-types/src/common/extent.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, SecondsFormat, Utc}; use serde::{Deserialize, Serialize, ser::SerializeSeq, ser::Serializer}; -use serde_with::DisplayFromStr; use utoipa::ToSchema; use crate::common::{Bbox, Crs}; @@ -15,7 +14,6 @@ pub struct Extent { } /// The spatial extent of the features in the collection. -#[serde_with::serde_as] #[derive(Serialize, Deserialize, ToSchema, Debug, PartialEq, Clone)] pub struct SpatialExtent { /// One or more bounding boxes that describe the spatial extent of the @@ -30,7 +28,6 @@ pub struct SpatialExtent { /// Extensions may support additional coordinate reference systems and add /// additional enum values. #[serde(default)] - #[serde_as(as = "Option")] #[schema(value_type = String)] pub crs: Option, } @@ -39,7 +36,7 @@ impl Default for SpatialExtent { fn default() -> Self { Self { bbox: vec![Bbox::Bbox2D([-180.0, -90.0, 180.0, 90.0])], - crs: Default::default(), + crs: Some(Crs::default2d()), } } } diff --git a/ogcapi-types/src/common/link.rs b/ogcapi-types/src/common/link.rs index ea865fc..8ae18c9 100644 --- a/ogcapi-types/src/common/link.rs +++ b/ogcapi-types/src/common/link.rs @@ -2,8 +2,8 @@ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; /// Hyperlink to enable Hypermedia Access -#[serde_with::skip_serializing_none] #[derive(Serialize, Deserialize, ToSchema, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] pub struct Link { /// Supplies the URI to a remote resource (or resource fragment). pub href: String, @@ -11,16 +11,28 @@ pub struct Link { pub rel: String, /// A hint indicating what the media type of the result of dereferencing /// the link should be. + #[serde(skip_serializing_if = "Option::is_none")] #[schema(nullable = false)] pub r#type: Option, + /// This flag set to true if the link is a URL template. + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(nullable = false)] + pub templated: Option, + /// A base path to retrieve semantic information about the variables used in URL template. + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(nullable = false)] + pub var_base: Option, /// A hint indicating what the language of the result of dereferencing the /// link should be. + #[serde(skip_serializing_if = "Option::is_none")] #[schema(nullable = false)] pub hreflang: Option, /// Used to label the destination of a link such that it can be used as a /// human-readable identifier. + #[serde(skip_serializing_if = "Option::is_none")] #[schema(nullable = false)] pub title: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[schema(nullable = false)] pub length: Option, } @@ -32,6 +44,8 @@ impl Link { href: href.to_string(), rel: rel.to_string(), r#type: None, + templated: None, + var_base: None, hreflang: None, title: None, length: None, @@ -44,6 +58,18 @@ impl Link { self } + /// Sets whether the link is templated + pub fn templated(mut self, templated: bool) -> Link { + self.templated = Some(templated); + self + } + + /// Sets the base path to retreive semantic information about the variables used in URL template + pub fn var_base(mut self, var_base: impl ToString) -> Link { + self.var_base = Some(var_base.to_string()); + self + } + /// Sets the language of the Link and returns the Value pub fn language(mut self, language: impl ToString) -> Link { self.hreflang = Some(language.to_string()); diff --git a/ogcapi-types/src/common/link_rel.rs b/ogcapi-types/src/common/link_rel.rs index 972dcf2..661e595 100644 --- a/ogcapi-types/src/common/link_rel.rs +++ b/ogcapi-types/src/common/link_rel.rs @@ -22,7 +22,7 @@ pub const DATA: &str = "data"; /// Identifies general metadata for the context (dataset or collection) that is primarily intended for consumption by machines. /// /// See: -pub const DATA_META: &str = "data-meta"; +pub const DATA_META: &str = "http://www.opengis.net/def/rel/ogc/1.0/data-meta"; /// Refers to a resource providing information about the link’s context. pub const DESCRIBEDBY: &str = "describedby"; @@ -30,12 +30,12 @@ pub const DESCRIBEDBY: &str = "describedby"; /// The target URI points to exceptions of a failed process. /// /// See: -pub const EXCEPTIONS: &str = "exceptions"; +pub const EXCEPTIONS: &str = "http://www.opengis.net/def/rel/ogc/1.0/exceptions"; /// The target URI points to the execution endpoint of the server. /// /// See: -pub const EXECUTE: &str = "execute"; +pub const EXECUTE: &str = "http://www.opengis.net/def/rel/ogc/1.0/execute"; pub const FIRST: &str = "first"; @@ -46,7 +46,7 @@ pub const ITEMS: &str = "items"; /// The target URI points to the list of jobs. /// /// See: -pub const JOB_LIST: &str = "job-list"; +pub const JOB_LIST: &str = "http://www.opengis.net/def/rel/ogc/1.0/job-list"; pub const LAST: &str = "last"; @@ -67,7 +67,7 @@ pub const PREV: &str = "prev"; /// The target URI points to the list of processes the API offers. /// /// See: -pub const PROCESSES: &str = "processes"; +pub const PROCESSES: &str = "http://www.opengis.net/def/rel/ogc/1.0/processes"; pub const RELATED: &str = "related"; @@ -105,12 +105,17 @@ pub const TILES: &str = "tiles"; /// The target IRI points to a resource that describes how to provide tile sets of the context resource in vector format. /// /// See: -pub const TILESETS_VECTOR: &str = "tilesets-vector"; +pub const TILESETS_VECTOR: &str = "http://www.opengis.net/def/rel/ogc/1.0/tilesets-vector"; /// The target IRI points to a resource that describes the TileMatrixSet according to the 2D-TMS standard. /// /// See: -pub const TILING_SCHEME: &str = "tiling-scheme"; +pub const TILING_SCHEME: &str = "http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme"; + +/// The target IRI points to a resource that lists one or more TileMatrixSets according to the 2D-TMS standard. +/// +/// See: +pub const TILING_SCHEMES: &str = "http://www.opengis.net/def/rel/ogc/1.0/tiling-schemes"; pub const OVERVIEW: &str = "overview"; diff --git a/ogcapi-types/src/common/media_type.rs b/ogcapi-types/src/common/media_type.rs index 832d222..7910b1d 100644 --- a/ogcapi-types/src/common/media_type.rs +++ b/ogcapi-types/src/common/media_type.rs @@ -24,6 +24,9 @@ pub const OPEN_API_YAML: &str = "application/vnd.oai.openapi+yaml;version=3.0"; /// Media Type for `application/vnd.mapbox.style+json` pub const MAPBOX_STYLE: &str = "application/vnd.mapbox.style+json"; +/// Media Type for `application/vnd.mapbox-vector-tile` +pub const MVT: &str = "application/vnd.mapbox-vector-tile"; + /// Media Type for `image/png` pub const PNG: &str = "image/png"; diff --git a/ogcapi-types/src/common/query.rs b/ogcapi-types/src/common/query.rs index 7845a6d..3c32cac 100644 --- a/ogcapi-types/src/common/query.rs +++ b/ogcapi-types/src/common/query.rs @@ -5,14 +5,13 @@ use utoipa::ToSchema; use crate::common::{Bbox, Crs, Datetime}; #[serde_with::serde_as] -#[derive(Deserialize, ToSchema, Debug, Clone)] +#[derive(Deserialize, ToSchema, Debug, Clone, Default)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub struct Query { #[serde(default, skip_serializing_if = "Option::is_none")] #[serde_as(as = "Option")] pub bbox: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - #[serde_as(as = "Option")] #[schema(value_type = String)] pub bbox_crs: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -25,7 +24,7 @@ pub struct Query { } /// Query parameters to facilitate pagination with a limit and offset -#[derive(Serialize, Deserialize, ToSchema, Debug, Clone, PartialEq, Eq)] +#[derive(Serialize, Deserialize, ToSchema, Debug, Clone, PartialEq, Eq, Default)] pub struct LimitOffsetPagination { /// Amount of items to return #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/ogcapi-types/src/edr/query.rs b/ogcapi-types/src/edr/query.rs index 97c3f97..eb11920 100644 --- a/ogcapi-types/src/edr/query.rs +++ b/ogcapi-types/src/edr/query.rs @@ -32,7 +32,6 @@ pub struct Query { pub datetime: Option, pub parameter_name: Option, #[serde(default)] - #[serde_as(as = "Option")] #[param(value_type = String)] pub crs: Option, pub f: Option, diff --git a/ogcapi-types/src/features/query.rs b/ogcapi-types/src/features/query.rs index f9eeb5c..096e3ec 100644 --- a/ogcapi-types/src/features/query.rs +++ b/ogcapi-types/src/features/query.rs @@ -7,7 +7,7 @@ use utoipa::{IntoParams, ToSchema}; use crate::common::{Bbox, Crs, Datetime}; #[serde_with::serde_as] -#[derive(Serialize, Deserialize, IntoParams, Debug, Clone)] +#[derive(Serialize, Deserialize, IntoParams, Debug, Default)] #[serde(rename_all = "kebab-case")] pub struct Query { /// The optional limit parameter limits the number of items that are @@ -64,7 +64,6 @@ pub struct Query { #[param(value_type = Bbox, style = Form, explode = false, nullable = false)] pub bbox: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - #[serde_as(as = "Option")] #[param(value_type = String, nullable = false)] pub bbox_crs: Option, /// Either a date-time or an interval. Date and time expressions adhere to @@ -87,7 +86,6 @@ pub struct Query { #[param(value_type = String)] pub datetime: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - #[serde_as(as = "Option")] #[param(value_type = String)] pub crs: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -97,7 +95,6 @@ pub struct Query { #[param(inline, nullable = false)] pub filter_lang: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - #[serde_as(as = "Option")] #[param(value_type = String)] pub filter_crs: Option, /// Parameters for filtering on feature properties @@ -105,7 +102,7 @@ pub struct Query { pub additional_parameters: HashMap, } -#[derive(Serialize, Deserialize, ToSchema, Default, Debug, Clone)] +#[derive(Serialize, Deserialize, ToSchema, Default, Debug)] #[serde(rename_all = "kebab-case")] pub enum FilterLang { #[default] diff --git a/ogcapi-types/src/tiles/mod.rs b/ogcapi-types/src/tiles/mod.rs index 62464a0..0ad9a18 100644 --- a/ogcapi-types/src/tiles/mod.rs +++ b/ogcapi-types/src/tiles/mod.rs @@ -6,9 +6,10 @@ mod tms; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; +use serde_with::DisplayFromStr; use utoipa::{IntoParams, ToSchema}; -// use crate::common::Crs; +use crate::common::Crs; /// A 2DPoint in the CRS indicated elsewere type Point2D = [f64; 2]; @@ -18,7 +19,8 @@ type Point2D = [f64; 2]; #[serde(untagged)] pub enum TilesCrs { /// Simplification of the object into a url if the other properties are not present - Simple(String), + #[schema(value_type = String)] + Simple(Crs), /// Reference to one coordinate reference system (CRS) Uri { uri: String }, /// An object defining the CRS using the JSON encoding for Well-known text @@ -31,6 +33,34 @@ pub enum TilesCrs { }, } +#[serde_with::serde_as] +#[derive(Serialize, Deserialize, ToSchema, IntoParams, Debug)] +#[serde(rename_all = "camelCase")] +pub struct TileParams { + /// Identifier selecting one of the TileMatrixSetId supported by the resource. + pub tile_matrix_set_id: TileMatrixSetId, + /// Identifier selecting one of the scales defined in the TileMatrixSet + /// and representing the scaleDenominator the tile. + pub tile_matrix: String, + /// Row index of the tile on the selected TileMatrix. It cannot exceed + /// the MatrixWidth-1 for the selected TileMatrix. + #[serde_as(as = "DisplayFromStr")] + pub tile_row: u32, + /// Column index of the tile on the selected TileMatrix. It cannot exceed + /// the MatrixHeight-1 for the selected TileMatrix. + #[serde_as(as = "DisplayFromStr")] + pub tile_col: u32, +} + +#[derive(Serialize, Deserialize, IntoParams, Debug)] +#[serde(rename_all = "camelCase")] +pub struct CollectionTileParams { + /// Local identifier of a vector tile collection + pub collection_id: String, + #[serde(flatten)] + pub tile_params: TileParams, +} + #[derive(Serialize, Deserialize, IntoParams, Debug)] #[serde(rename_all = "kebab-case")] #[into_params(parameter_in = Query)] diff --git a/ogcapi-types/src/tiles/tms.rs b/ogcapi-types/src/tiles/tms.rs index ebfb5f3..b1a71c7 100644 --- a/ogcapi-types/src/tiles/tms.rs +++ b/ogcapi-types/src/tiles/tms.rs @@ -1,5 +1,9 @@ +use std::{ + fmt::Display, + num::{NonZeroU16, NonZeroU64}, +}; + use serde::{Deserialize, Serialize}; -use std::num::{NonZeroU16, NonZeroU64}; use utoipa::ToSchema; use crate::common::Link; @@ -16,6 +20,16 @@ pub enum TileMatrixSetId { // WorldCRS84Quad, // GNOSISGlobalGrid, // WorldMercatorWGS84Quad, + Custom(String), +} + +impl Display for TileMatrixSetId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TileMatrixSetId::WebMercatorQuad => write!(f, "WebMercatorQuad"), + TileMatrixSetId::Custom(name) => write!(f, "{name}"), + } + } } /// A definition of a tile matrix set following the Tile Matrix Set standard. @@ -81,9 +95,7 @@ pub struct TileMatrixSet { #[schema(nullable = false)] pub uri: Option, /// Coordinate Reference System (CRS) - #[serde(default, skip_serializing_if = "Option::is_none")] - #[schema(nullable = false)] - pub crs: Option, + pub crs: TilesCrs, /// Ordered list of names of the dimensions defined in the CRS #[schema(min_items = 1, inline = true)] #[serde(default, skip_serializing_if = "Vec::is_empty")] @@ -103,8 +115,6 @@ pub struct TileMatrixSet { /// A tile matrix, usually corresponding to a particular zoom level of a /// TileMatrixSet. -#[serde_with::serde_as] -#[serde_with::skip_serializing_none] #[derive(Serialize, Deserialize, ToSchema, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct TileMatrix { @@ -112,10 +122,12 @@ pub struct TileMatrix { /// and representing the scaleDenominator the tile. pub id: String, /// Title of a tile matrix, normally used for display to a human + #[serde(skip_serializing_if = "Option::is_none")] #[schema(nullable = false)] pub title: Option, /// Brief narrative description of a tile matrix, normally available /// for display to a human + #[serde(skip_serializing_if = "Option::is_none")] #[schema(nullable = false)] pub description: Option, /// Unordered list of one or more commonly used or formalized word(s) or @@ -129,8 +141,8 @@ pub struct TileMatrix { /// The corner of the tile matrix (_topLeft_ or /// _bottomLeft_) used as the origin for numbering tile rows and columns. /// This corner is also a corner of the (0, 0) tile. - #[schema(nullable = false)] - pub corner_of_origin: Option, + #[serde(default)] + pub corner_of_origin: CornerOfOrigin, /// Precise position in CRS coordinates of the corner of origin (e.g. the /// top-left corner) for this tile matrix. This position is also a corner /// of the (0, 0) tile.