geopython / geopython/pygeoapi

Support a passthrough for HTTP Range requests on /collections/{id}/coverage for COG-aware clients

Open
#2,337 4 comments 0 reactions 1 assignee Claimed by @tomkralidis View on GitHub
enhancement OGC API - Coverages
Dominant language
Python
Stars
624
Forks
326
Avg merge
8h 25m
Merged PRs (30d)
2

Description

Currently, mapscript is required for serving tiles via OGC Maps from rasters. However, most modern map library clients allow range requests to fetch IFD headers and the specific tile/strip byte ranges they need from COGs. This has become a common "serverless" format. I'd like to propose using the coverage endpoint, with the rasterio provider, to support serving COGs directly for ingestion by clients like [OpenLayers](https://openlayers.org/en/latest/apidoc/module-ol_source_GeoTIFF.html).

The [OGC Coverages standard 19.4](https://docs.ogc.org/DRAFTS/19-087.html#_overview_15) defines, "Support for accessing a coverage response as a [COG](https://docs.ogc.org/is/21-026/21-026.html) using HTTP range requests.". There are two requirements defined (26 and 27). Requirement 26 defines that the coverage endpoint shall support range requests and that it shall do so when there are no query parameters. Requirement 27 speaks to content negotiation, however that is not crucial for web clients that want to access tiffs via range requests.

I propose adding a passthrough capability for serving COGs whereas if no query parameters are passed, then the response is served via a range request for the bytes requested by the client.

I have been testing this locally with COGs and I made the following modifications:

- Created a "coverage_passthrough" setting in pygeoapi_settings.yaml. When true it indicates that the coverage will allow a range request.

- pygeoapi/api/coverages.py
- Created a function to check if there are parameters sent with the request. If yes then do not allow a passthrough range request, if no then allow it.

- flask_app.py
- Modified collection_coverage function to serve the COG directly:

```
passthrough_enabled = api_.config.get('server', {}).get(
'coverage_passthrough', True
)
if passthrough_enabled:
cfg = api_.config['resources'].get(collection_id, {})
provider_def = next(
(p for p in cfg.get('providers', [])
if p.get('type') == 'coverage'),
None
)

if (provider_def
and isinstance(provider_def.get('data'), str)
and os.path.isfile(provider_def['data'])
and coverages_api.is_passthrough_coverage_request(
request.args, request.headers, provider_def)):
fmt = provider_def.get('format') or {}
return send_file(
provider_def['data'],
mimetype=fmt.get('mimetype'),
conditional=True,
download_name=os.path.basename(provider_def['data']),
as_attachment=False
)
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.