Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6e26b96
Update readme with new env variables
Teddy-1000 Oct 22, 2025
6e1c55d
Add configurable openapi metadata and j2 dataset metadata template
Teddy-1000 Oct 22, 2025
ff523cd
Explain how and where to mount new files
Teddy-1000 Oct 27, 2025
5ae54dc
Move default openapi_metadata to a file, and just load the file as de…
Teddy-1000 Oct 27, 2025
0251c78
Explain how and where to mount new files
Teddy-1000 Oct 27, 2025
e667144
Add absolute path as default
Teddy-1000 Oct 27, 2025
65181ce
Update API README
Teddy-1000 Oct 27, 2025
3a4f6f1
Fix formatting
Teddy-1000 Oct 27, 2025
ef02f01
Merge remote-tracking branch 'origin/main' into add-option-for-custom…
Teddy-1000 Dec 2, 2025
2a6800e
Update default j2 dataset template
Teddy-1000 Dec 2, 2025
56a9e36
Fix line length
Teddy-1000 Dec 2, 2025
7550eaa
Update api/templates/dataset_metadata_template.j2
Teddy-1000 Dec 8, 2025
5fe4eeb
Update api/templates/dataset_metadata_template.j2
Teddy-1000 Dec 8, 2025
383f43b
Remove print
Teddy-1000 Dec 8, 2025
45d96f2
Clearify that the openapi metadata is also used for the landing page
Teddy-1000 Dec 8, 2025
6a03583
Merge branch 'add-option-for-custom-openapi-metadata' of github.com:E…
Teddy-1000 Dec 8, 2025
dba41cb
Use fastAPI openapi wrapper to mitigate passing arbitrary arguemnts t…
Teddy-1000 Jan 14, 2026
84e15f5
Update readme about custom openapi contents
Teddy-1000 Jan 14, 2026
fa8cbdf
Revert "Use fastAPI openapi wrapper to mitigate passing arbitrary arg…
Teddy-1000 Jan 16, 2026
a53775f
Make list of acceptable parameters to take from openapi_metadata.json
Teddy-1000 Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,36 @@ Environment variables that can be used to configure the container or the environ
| GUNICORN_CMD_ARGS | Command-line arguments for configuring Gunicorn, a Python WSGI HTTP Server. | ☐ |
| CORS_ORIGINS | Indicates whether the response can be shared with requesting code from the given origins (passed as a comma separated string) | ☐ |
| CORS_HEADERS | Indicates what headers should be supported with cross-origin requests (passed as a comma separated string) | ☐ |
| JINJA2_TEMPLATES | Path to a folder with jinja2 templates to override the default templates used by the API. See template section for details | ☐ |
| OPENAPI_METADATA_PATH | Path to an alternative OpenAPI metadata json file. It need to have the same fields as the openapi/openapi_metadata.py file. | ☐ |

## OpenAPI and EDR landing page metadata

To load your own metadata file, mount your new openapi metadata json file, with the same structure and key as the default one, found in openapi_default_files, and point the environment variable `OPENAPI_METADATA_PATH` to the new file. If you mount your folder to the same as the default one, make sure to not overwrite files you have not replaced.

This Metdata is used both for the OpenAPI specification and the EDR landing page.

The available fields are: title, version, summary, description, terms_of_service, contact, license_info, tags, external_docs.

## JINJA2_TEMPLATES

The jinja2 folder must container the following files:

- dataset_metadata_template.j2: this is the metadata template for the observation collection.

### dataset_metadata_template.j2

The current jinja2 filters will be replaced. It's important to use the json j2 filter when inserting these strings.

- spatial_extent
- temporal_extent
- url_base
- url_conformance
- url_docs

All url fields are dynamically generated based on the request url base.

To load a custom template, mount a folder with your new template in to the container and set the `JINJA2_TEMPLATES` environment variable to point your new folder.

## Prerequisites of running locally

Expand Down
37 changes: 21 additions & 16 deletions api/openapi/openapi_metadata.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
openapi_metadata = {
"title": "EDR Observations API Europe EUMETNET",
"description": (
"OGC EDR API data service for European meteorological observations from EUMETNET,"
" co-funded by the European Union."
),
"contact": {
"name": "EUMETNET",
"url": "https://www.eumetnet.eu/about-us/",
"email": "eucos@metoffice.gov.uk",
},
"license_info": {
"name": "CC-BY-4.0",
"url": "https://creativecommons.org/licenses/by/4.0/",
},
}
import os
import json

with open(
os.getenv("OPENAPI_METADATA_PATH", "/app/openapi_default_files/openapi_metadata.json"),
"r",
) as file:
openapi_metadata = json.load(file)
valid_keys = [
"title",
"version",
"summary",
"description",
"terms_of_service",
"contact",
"license_info",
"openapi_tags",
]
unwanted = set(openapi_metadata) - set(valid_keys)
for unwanted_key in unwanted:
del openapi_metadata[unwanted_key]
13 changes: 13 additions & 0 deletions api/openapi_default_files/openapi_metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"title": "EDR Observations API Europe EUMETNET",
"description": "OGC EDR API data service for European meteorological observations from EUMETNET, co-funded by the European Union.",
"contact": {
"name": "EUMETNET",
"url": "https://www.eumetnet.eu/about-us/",
"email": "eucos@metoffice.gov.uk"
},
"license_info": {
"name": "CC-BY-4.0",
"url": "https://creativecommons.org/licenses/by/4.0/"
}
}
8 changes: 3 additions & 5 deletions api/routers/feature.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
from typing import Annotated

import datastore_pb2 as dstore
Expand All @@ -23,7 +24,7 @@

router = APIRouter(prefix="/collections/observations")

env = Environment(loader=FileSystemLoader("templates"), autoescape=select_autoescape())
env = Environment(loader=FileSystemLoader(os.getenv("JINJA2_TEMPLATES", "templates")), autoescape=select_autoescape())


@router.get(
Expand Down Expand Up @@ -174,10 +175,7 @@ async def get_dataset_metadata(request: Request):
]
],
"temporal_extents": [
[
f"{extent.temporal_extent.start.ToDatetime().strftime('%Y-%m-%dT%H:%M:%SZ')}",
f"{extent.temporal_extent.end.ToDatetime().strftime('%Y-%m-%dT%H:%M:%SZ')}",
],
[f"{extent.temporal_extent.start.ToDatetime().strftime('%Y-%m-%dT%H:%M:%SZ')}", ".."],
],
"url_base": base_url,
"url_conformance": base_url + "conformance",
Expand Down
231 changes: 131 additions & 100 deletions api/templates/dataset_metadata_template.j2
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"id": "urn:wmo:md:eu-eumetnet-observations:swob-realtime",
"id": "urn:wmo:md:eu-eumetnet-surface-observations:land-station-observations",
"conformsTo": [
"http://wis.wmo.int/spec/wcmp/2/conf/core"
],
"type": "Feature",
"http://wis.wmo.int/spec/wcmp/2/conf/core"
],
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": {{ spatial_extents }}
Expand All @@ -12,105 +12,136 @@
"interval": {{ temporal_extents|tojson }},
"resolution": "PT10M"
},
"properties": {
"title": "Land surface weather observations",
"description": "Land surface observations measured at automatic and manual stations of EUMETNET Members (last 24 hours)",
"themes": [
{
"concepts": [
{
"id": "weather"
}
],
"scheme": "https://codes.wmo.int/wis/topic-hierarchy/earth-system-discipline"
},
{
"concepts": [
{
"id": "surface-based-observations"
}
],
"scheme": "https://codes.wmo.int/wis/topic-hierarchy/earth-system-discipline/_weather"
}
"properties": {
"title": "Land surface weather observations",
"description":"Land surface observations measured at automatic and manual weather stations of EUMETNET Members and their trusted partners (last 24 hours only)",
"contacts": [
{
"name": "Met Norway",
"organization": "National Meteorological service of Norway, Met Norway",
"phones": [
{
"value": "+4722963000"
}
],
"language": "en",
"type": "dataset",
"created": "2023-01-01T00:00:00Z",
"updated": "2024-09-19T00:00:00Z",
"contacts": [
{
"organization": "EUMETNET",
"addresses": [
{
"deliveryPoint": [
"Avenue Circulaire 3"
],
"city": "Bruxelles",
"postalCode": "1180",
"country": "Belgique"
}
],
"links": [
{
"href": "https://www.eumetnet.eu/about-us/",
"rel": "about",
"type": "text/html"
}
],
"roles": [
"host"
]
}
"emails": [
{
"value": "post@met.no"
}
],
"keywords": [
"surface weather",
"observations",
"meteorology"
"addresses": [
{
"deliveryPoint": [
"https://www.met.no/en/contact-us"
],
"name": "Met Norway",
"city": "Oslo",
"postalCode": "0313",
"country": "Norway"
}
],
"wmo:dataPolicy": "core"
},
"links": [
{
"href": "E-SOH dataset mqtt stream",
"rel": "items",
"title": "E-SOH dataset data notifications",
"type": "application/json"
},
{
"href": "E-SOH time series mqtt stream",
"rel": "items",
"title": "E-SOH time series data notifications",
"type": "application/json"
},
{
"href": {{ url_base|tojson }},
"rel": "data",
"title": "E-SOH EDR API landing page",
"type": "application/json"
},
{
"href": {{ url_docs|tojson }},
"rel": "related",
"title": "E-SOH API documentation",
"type": "application/json"
},
{
"href": {{ url_conformance|tojson }},
"rel": "conformance",
"title": "E-SOH Conformance Declaration",
"type": "application/json"
},
{
"href": "https://www.eumetnet.eu/wp-content/uploads/2018/03/List-of-current-Members-as-pdf.pdf",
"links": [
{
"rel": "about",
"title": "EUMETNET Members",
"type": "text/html"
"type": "text/html",
"href": "https://www.eumetnet.eu/about-us"
}
],
"roles": ["host"]
}
],
"keywords": [
"weather",
"surface-based observations",
"observations",
"meteorology",
"surface weather",
"Norway",
"Finland",
"The Netherlands"
],
"themes": [
{ "concepts": [
{
"id":"weather",
"title": "Weather",
"url": "https://codes.wmo.int/wis/topic-hierarchy/earth-system-discipline/weather"
}
],
"scheme":"https://codes.wmo.int/wis/topic-hierarchy/earth-system-discipline"
},
{ "concepts": [
{
"id":"surface-based-observations"
}
],
"scheme":"https://codes.wmo.int/wis/topic-hierarchy/earth-system-discipline/weather/surface-based-observations"
},
{
"href": "https://creativecommons.org/licenses/by/4.0/",
"rel": "license",
"title": "Creative Commons BY 4.0 licence",
"type": "text/html"
}
]
{
"concepts": [
{
"id": "air_temperature",
"title": "Air temperature",
"url": "http://vocab.nerc.ac.uk/standard_name/air_temperature/"
},
{
"id": "wind_speed",
"title": "Wind Speed",
"url": "http://vocab.nerc.ac.uk/standard_name/wind_speed/"
},
{
"id": "wind_to_direction",
"title": "Wind to diection",
"url": "http://vocab.nerc.ac.uk/standard_name/wind_to_direction/"
}
],
"scheme": "https://vocab.nerc.ac.uk/standard_name"
}
],
"language":"en",
"created": "2025-06-04T14:00:00Z",
"updated": "2025-06-04T14:00:00Z",
"rights": "Users are granted free and unrestricted access to this data, without charge and with no conditions on use. Users are requested to attribute the producer of this data. WMO Unified Data Policy (Resolution 1 (Cg-Ext 2021))",
"licence": "CC BY 4.0 Creative Commons license",
"type": "dataset",
"wmo:dataPolicy": "recommended"
},
"links": [
{
"href": {{ url_base|tojson }},
"rel": "data",
"title": "E-SOH EDR API landing page",
"type": "application/json"
},
{
"href": {{ url_docs|tojson }},
"rel": "related",
"title": "E-SOH API documentation",
"type": "application/json"
},
{
"href": {{ url_conformance|tojson }},
"rel": "conformance",
"title": "E-SOH Conformance Declaration",
"type": "application/json"
},
{
"rel": "related",
"href": "https://www.eumetnet.eu/observations/observations-data-sharing-2/",
"type": "text/html",
"title": "Documentation related to EUMETNET data sharing"
},
{
"rel": "related",
"href": "https://www.eumetnet.eu/about-us-2/members-partners/",
"type": "text/html",
"title": "List of EUMETNET Members"
},
{
"rel": "license",
"href": "https://creativecommons.org/licenses/by/4.0/",
"title": "Creative Commons BY 4.0 licence",
"type": "text/html"
}
]
}