Skip to content

PB-2354: Add CF extension fields - #656

Merged
asteiner-swisstopo merged 12 commits into
developfrom
feat-PB-2354-implement-cf-extension
Aug 20, 2026
Merged

PB-2354: Add CF extension fields#656
asteiner-swisstopo merged 12 commits into
developfrom
feat-PB-2354-implement-cf-extension

Conversation

@asteiner-swisstopo

@asteiner-swisstopo asteiner-swisstopo commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

This adds CF extension fields cf:standard_names and unit to a STAC Item.

Example:

"properties": {
                ...
                "cf:standard_name": "air_temperature",
                "unit": "K",
            },

It follows the example of the forecast extension properties. So we add the fields to:

  1. ... the model and the serializers, so it's available in the Item/Feature endpoints.
  2. ... the admin UI.
  3. ... the OpenAPI specs.
image

The more peculiar bits:

  1. Add the fields to the POST /search endpoint. However, as for the forecast extension fields, they are excluded from the GET request because of the colon : character in the URL.
  2. List the extension only if one of the two fields is present. For the forecast extension, this is only done for collection ch.meteoschweiz.ogd-forecasting-icon.
  3. Add the fields to the e2e test input. Not strictly necessary as the fields are optional but I figured it makes sense to keep this example representative of the DB schema.

As discussed:

  1. There is no additional validation like checking if cf:standard_name is from the CF Standard Name Table.
  2. cf:cell_methods / description are left out on purpose.
  3. As the list of properties in the OpenAPI specs is already quite long, we might want to group them by extension using the "Any of" feature. I propose we do this in the follow-up task PB-2355.

To be clarified: In #653 a trigger update_collection_child_trigger_etag_only was added to update the ETag if a field changed. I am not familiar with how this works, Do I have to add another trigger for the new properties?

@asteiner-swisstopo asteiner-swisstopo self-assigned this Jun 24, 2026
@github-actions github-actions Bot added the feature New feature or enhancement label Jun 24, 2026
@asteiner-swisstopo
asteiner-swisstopo force-pushed the feat-PB-2354-implement-cf-extension branch 3 times, most recently from e14bbe0 to 2bb5a64 Compare June 25, 2026 12:10

@boecklic boecklic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot.
I'd generalize handling of extension, see comment below.

Comment thread app/stac_api/models/item.py
Comment thread app/stac_api/serializers/item.py Outdated
@asteiner-swisstopo

Copy link
Copy Markdown
Contributor Author

On hold until this is merged: #658

@asteiner-swisstopo asteiner-swisstopo added DO NOT MERGE 💣 Guard to prevent accidental merging of this PR WIP 🚧 Work in progress: workflow will prevent merging a PR with this label and removed DO NOT MERGE 💣 Guard to prevent accidental merging of this PR labels Jul 16, 2026
@asteiner-swisstopo
asteiner-swisstopo force-pushed the feat-PB-2354-implement-cf-extension branch 2 times, most recently from b1c9186 to 2dd8fe6 Compare August 11, 2026 15:58
@asteiner-swisstopo

Copy link
Copy Markdown
Contributor Author

Finally adapted to the changes made in #658: Can you have another look please @boecklic?

To test manually:

  1. Run the STAC API server: make setup && make serve

  2. In the Django admin UI, add a token, here for example b5c95f9615d610635c88226d03344fee8ea2ea67.

  3. Create a collection using that token:

    curl -X PUT \
      "http://127.0.0.1:8000/api/stac/v1/collections/cf-collection" \
      -H "Content-Type: application/json" \
      -H "Authorization: Token b5c95f9615d610635c88226d03344fee8ea2ea67" \
      -d '{
        "id": "cf-collection",
        "description": "A test collection",
        "license": "Test license"
      }'
  4. Enable the CF extension in the Admin UI:
    image

Then create a feature with the putFeature endpoint (:warning: declare the CF extension in stac_extensions!):

curl -X PUT \
  "http://127.0.0.1:8000/api/stac/v1/collections/cf-collection/items/cf-item-1" \
  -H "Content-Type: application/json" \
  -H "Authorization: Token b5c95f9615d610635c88226d03344fee8ea2ea67" \
  -d '{
    "id": "cf-item-1",
    "geometry": {
      "type": "Point",
      "coordinates": [8.5417, 47.3769]
    },
    "properties": {
        "created": "2018-02-12T23:20:50Z",
        "datetime": "2018-02-12T23:20:50Z",
        "updated": "2018-02-12T23:20:50Z",
        "title": "Feature title",
        "cf:standard_name": "air_temperature",
        "unit": "K"
    },
    "links": [],
    "stac_extensions": ["https://stac-extensions.github.io/cf/v1.0.0/schema.json"]
  }'

Which can also be retrieved through the POST /search endpoint as with the forecast:* properties:

curl -X POST \
  "http://127.0.0.1:8000/api/stac/v1/search" \
  -H "Content-Type: application/json" \
  -H "Authorization: Token b5c95f9615d610635c88226d03344fee8ea2ea67" \
  -d '{
    "cf:standard_name": "air_temperature"
  }'

@asteiner-swisstopo asteiner-swisstopo removed the WIP 🚧 Work in progress: workflow will prevent merging a PR with this label label Aug 11, 2026
@asteiner-swisstopo
asteiner-swisstopo force-pushed the feat-PB-2354-implement-cf-extension branch from 2dd8fe6 to 7be895d Compare August 11, 2026 17:08
@asteiner-swisstopo

asteiner-swisstopo commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

From offline discussion with @boecklic regarding POST /search:

Currently, to query /search for a field in properties, you need to send a payload like this

"query": {
    "title": {
       "eq": "CF Collection 1"
    }
}

For the other fields, you can just do

{
    "datetime": "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z"
}

This is in accordance with the query extension. However, currently "query" only accepts the following fields:

  • title
  • created
  • updated

For the forecast:* properties, it works as for the other fields:

{
    "forecast:variable": "ALB_RAD"
}

But this is a bug! It should work like this:

"query": {
    "forecast:variable": {
       "eq": "ALB_RAD"
    }
}

So for the CF fields, we should do it correctly from the start: They need to be queried through the "query" field. We agreed to do that in the scope of this ticket.

Other possible follow-ups identified:

  1. Also move the forecast:* properties into the "query" field for POST /search.
  2. The /conformance endpoint does not list the query extension even though it is supported (albeit partly non-compliant).
  3. The query extensions docs say "It is recommended to implement the Filter Extension instead of the Query Extension.": We might want to do that in the course of the transition to SWISSGEO.

Not strictly necessary as it's an optional field but added nonetheless
to keep it representative of the schema.
This avoids pylints warning "too-many-branches"
Created with `python app/manage.py makemigrations`.
As for the other extensions (see PB-2355, #658), the CF extension needs
to be
- enabled on Collection level (`stac_extensions_enabled`)
- declared on Item level (`stac_extensions`)
Initially, this followed the example of the `forecast:*` fields (see
PB-1169, #500). But that turned out to be wrong: Actually, as we
implement the [query
extension](https://github.com/stac-api-extensions/query), we are
supposed to filter property fields through the "query" param.

We keep it for the `forecast:*` fields for the moment in order to avoid
breaking changes. For the CF extension fields, we do it right from the
start.

While doing this, I found another bug: We cannot query multiple fields
as it is supposed to work. Will be fixed in a follow up where we replace
the Query Extension by the Filter Extension. I added a comment and a
disabled test to document that.

Technical sidenote: Currently, there are two locations where external
API field names are mapped to internal Django names:

- `ItemQuerySet.filter_by_query`: Used by the "query" param in
  postSearchSTAC
- `ItemsPropertiesSerializer.to_internal_value`: Used e.g. by putFeature

I tried to bring this together at first to avoid duplication. It turned
out, however, that it's not quite the same:

- `ItemQuerySet.filter_by_query`: Maps to Django model field
- `ItemsPropertiesSerializer.to_internal_value`: Maps to DRF Serializer
  field

So, unfortunately, these have to be kept separate. For
`filter_by_query`, it's just a simple replacement of colon by
underscore, e.g., `cf:standard_name` to `cf_standard_name`.

We don't need to prepend `cf:standard_name` and ``unit` with
`properties_` like e.g. `expires` which is `properties_expires`
internally. This is in line with how the fields of the Forecast
extension are handled.
@asteiner-swisstopo
asteiner-swisstopo force-pushed the feat-PB-2354-implement-cf-extension branch from d278c66 to de2ba15 Compare August 18, 2026 07:17
@asteiner-swisstopo asteiner-swisstopo changed the title PB-2354 Add CF extension fields PB-2354: Add CF extension fields Aug 18, 2026
@asteiner-swisstopo

asteiner-swisstopo commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Update: To filter by a CF field, you need to go through the "query" parameter in the POST /search endpoint. For example:

"query": {
    "cf:standard_name": {
       "eq": "air_temperature"
    }
}

This is now also reflected in the OpenAPI docs:
pb-2354-cf-extension-openapi-specs-search

⚠️ In the course of implementing this, I found a bug in the "query" parameter. It only takes into account the first field passed to it, the other are ignored. So, for example, here the "unit" field is ignored:

"query": {
    "cf:standard_name": {
       "eq": "air_temperature"
    },
    "unit": {
       "eq": "K"
    }
}

As agreed with @boecklic: We ignore this for the moment as POST /search does not seem to be used a lot. Instead, we fix this properly when moving service-stac to SWISSGEO by replacing the Query Extension with the Filter Extension. This is also what the Query extension recommends:

It is recommended to implement the Filter Extension instead of the Query Extension. Filter Extension is more well-defined, more expressive, and uses the standardized CQL2 query language instead of the proprietary language defined here. There is no plan to deprecate this extension, but it is also unlikely to see any further refinement or changes.

Impact on the geoadmin STAC Browser: Probably none - the Query Extension is not supported as far as I can see. The latest STAC Browser (not the geoadmin fork) is compatible with CQL, so I understand that it rather supports the Filter Extension instead of the (deprecated) Query Extension.

@boecklic boecklic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@asteiner-swisstopo
asteiner-swisstopo merged commit 527d3fe into develop Aug 20, 2026
7 checks passed
@asteiner-swisstopo
asteiner-swisstopo deleted the feat-PB-2354-implement-cf-extension branch August 20, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants