Skip to content

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

@mikefedak

Description

@mikefedak

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.

The OGC Coverages standard 19.4 defines, "Support for accessing a coverage response as a COG 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
            )

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions