Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 13 additions & 2 deletions api/metadata_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def datetime_to_iso_string(value: datetime) -> str:
iso_8601_str = value.isoformat()
tz_offset_utc = "+00:00"
if iso_8601_str.endswith(tz_offset_utc):
return f"{iso_8601_str[:-len(tz_offset_utc)]}Z"
return f"{iso_8601_str[: -len(tz_offset_utc)]}Z"
else:
return iso_8601_str

Expand Down Expand Up @@ -193,7 +193,11 @@ async def get_collection_metadata(base_url: str, collection_id: str, is_self) ->
title=collections_metadata[collection_id]["title"],
links=[
Link(href=f"{base_url}/observations", rel="self" if is_self else "data"),
Link(href=collections_metadata[collection_id]["license"]["url"], rel="license", type="text/html"),
Link(
href=collections_metadata[collection_id]["license"]["url"],
rel="license",
type="text/html",
),
],
extent=Extent(
spatial=Spatial(
Expand Down Expand Up @@ -261,6 +265,13 @@ async def get_collection_metadata(base_url: str, collection_id: str, is_self) ->
variables=Variables(query_type="area", output_format=["CoverageJSON"]),
)
),
radius=EDRQuery(
link=EDRQueryLink(
href=f"{base_url}/observations/radius",
rel="data",
variables=Variables(query_type="radius", output_format=["CoverageJSON"]),
)
),
),
crs=collections_metadata[collection_id]["crs"],
output_formats=["CoverageJSON"],
Expand Down
7 changes: 7 additions & 0 deletions api/openapi/edr_query_parameter_descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@
wigos_id = "WIGOS Station Identifier (WSI) of the station to query data from."
format = "Specify wanted return format."
point = "Point to query all data within 10 meters, specified as Well-Known Text (WKT) point coordinates."
point_radius = (
"Point to query data from within a specified radius,"
" specified as Well-Known Text (WKT) point coordinates."
"Coordinates are given as longitude then latitude (`lon` `lat`) in degrees. Only 2D points are supported."
)
area = "Area to query data from in Well-Known Text (WKT) polygon coordinates."
radius_within = "Defines the radius of area to query around the coordinates defined in the coords."
radius_within_unit = "Unit of the radius defined in within."
33 changes: 28 additions & 5 deletions api/openapi/openapi_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@
"METNO": {"summary": "Point in Oslo", "value": "POINT(10.72 59.9423)"},
}

within = {
"10 meters": {"value": "0.01"},
"100 meters": {"value": "0.1"},
"1 Kilometer": {"value": "1"},
}

within_unit = {"Default": {"value": "Kilometers"}}

polygon = {
"Netherlands": {
"summary": "Area in central Netherlands",
Expand All @@ -74,15 +82,21 @@
naming_authority = {
"Met.no": {"summary": "Norwegian Meteorological Institute", "value": "no.met"},
"FMI:": {"summary": "Finnish Meteorological Institute", "value": "fi.fmi"},
"KNMI": {"summary": "Royal Netherlands Meteorological Institute", "value": "nl.knmi"},
"KNMI": {
"summary": "Royal Netherlands Meteorological Institute",
"value": "nl.knmi",
},
}

institution = {
"Met.no": {
"summary": "Norwegian Meteorological Institute",
"value": "Norwegian Meteorological Institute (MET Norway)",
},
"FMI:": {"summary": "Finnish Meteorological Institute", "value": "Finnish Meteorological Institute (FMI)"},
"FMI:": {
"summary": "Finnish Meteorological Institute",
"value": "Finnish Meteorological Institute (FMI)",
},
"KNMI": {
"summary": "Royal Netherlands Meteorological Institute",
"value": "Royal Netherlands Meteorological Institute (KNMI)",
Expand All @@ -91,10 +105,16 @@

standard_name = {
"air_temperature": {"summary": "Air temperature", "value": "air_temperature"},
"wind_from_direction": {"summary": "Wind direction", "value": "wind_from_direction"},
"wind_from_direction": {
"summary": "Wind direction",
"value": "wind_from_direction",
},
"wind_speed": {"summary": "Wind speed", "value": "wind_speed"},
"relative_humidity": {"summary": "Relative humidity", "value": "relative_humidity"},
"air_pressure_at_sea_level": {"summary": "Air pressure at sea level", "value": "air_pressure_at_sea_level"},
"air_pressure_at_sea_level": {
"summary": "Air pressure at sea level",
"value": "air_pressure_at_sea_level",
},
}

unit = {
Expand All @@ -114,4 +134,7 @@
"PT10M": {"summary": "10 minutes", "value": "PT10M"},
}

method = {"mean": {"summary": "Mean", "value": "mean"}, "maximum": {"summary": "Maximum", "value": "maximum"}}
method = {
"mean": {"summary": "Mean", "value": "mean"},
"maximum": {"summary": "Maximum", "value": "maximum"},
}
142 changes: 137 additions & 5 deletions api/routers/edr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from typing import Annotated
from typing import Dict
from typing import Set
from typing import Literal


import datastore_pb2 as dstore
import formatters
Expand Down Expand Up @@ -174,7 +176,11 @@ async def get_locations(
)
async def get_data_location_id(
location_id: Annotated[
str, Path(description=edr_query_parameter_descriptions.wigos_id, openapi_examples=openapi_examples.wigos_id)
str,
Path(
description=edr_query_parameter_descriptions.wigos_id,
openapi_examples=openapi_examples.wigos_id,
),
],
parameter_name: Annotated[
str | None,
Expand All @@ -186,7 +192,10 @@ async def get_data_location_id(
] = None,
datetime: Annotated[
str | None,
Query(description=edr_query_parameter_descriptions.datetime, openapi_examples=openapi_examples.datetime),
Query(
description=edr_query_parameter_descriptions.datetime,
openapi_examples=openapi_examples.datetime,
),
] = None,
f: Annotated[
formatters.Formats, Query(description=edr_query_parameter_descriptions.format)
Expand Down Expand Up @@ -245,7 +254,11 @@ async def get_data_location_id(
)
async def get_data_position(
coords: Annotated[
str, Query(description=edr_query_parameter_descriptions.point, openapi_examples=openapi_examples.point)
str,
Query(
description=edr_query_parameter_descriptions.point,
openapi_examples=openapi_examples.point,
),
],
parameter_name: Annotated[
str | None,
Expand Down Expand Up @@ -338,7 +351,11 @@ async def get_data_position(
)
async def get_data_area(
coords: Annotated[
str, Query(description=edr_query_parameter_descriptions.area, openapi_examples=openapi_examples.polygon)
str,
Query(
description=edr_query_parameter_descriptions.area,
openapi_examples=openapi_examples.polygon,
),
],
parameter_name: Annotated[
str | None,
Expand All @@ -350,7 +367,10 @@ async def get_data_area(
] = None,
datetime: Annotated[
str | None,
Query(description=edr_query_parameter_descriptions.datetime, openapi_examples=openapi_examples.datetime),
Query(
description=edr_query_parameter_descriptions.datetime,
openapi_examples=openapi_examples.datetime,
),
] = None,
f: Annotated[
formatters.Formats, Query(description=edr_query_parameter_descriptions.format)
Expand Down Expand Up @@ -418,3 +438,115 @@ async def get_data_area(
response = formatters.formatters[f](observations)

return response


@router.get(
"/radius",
tags=["Collection data queries"],
response_model=Coverage | CoverageCollection,
response_model_exclude_none=True,
response_class=CoverageJsonResponse,
)
async def get_data_radius(
coords: Annotated[
str,
Query(
description=edr_query_parameter_descriptions.point_radius,
openapi_examples=openapi_examples.point,
),
],
within: Annotated[
int | float,
Query(
alias="within",
description=edr_query_parameter_descriptions.radius_within,
openapi_examples=openapi_examples.within,
),
],
within_units: Annotated[
Literal["Kilometers"],
Query(
alias="within-units",
description=edr_query_parameter_descriptions.radius_within_unit,
openapi_examples=openapi_examples.within_unit,
),
] = "Kilometers",
parameter_name: Annotated[
str | None,
Query(
alias="parameter-name",
description=edr_query_parameter_descriptions.parameter_name,
openapi_examples=openapi_examples.parameter_name,
),
] = None,
datetime: Annotated[
str | None,
Query(
description=edr_query_parameter_descriptions.datetime,
openapi_examples=openapi_examples.datetime,
),
] = None,
f: Annotated[
formatters.Formats, Query(description=edr_query_parameter_descriptions.format)
] = formatters.Formats.covjson,
standard_name: Annotated[
str | None,
Query(
description=edr_query_parameter_descriptions.standard_name,
openapi_examples=custom_dimension_examples.standard_name,
),
] = None,
level: Annotated[
str | None,
Query(
description=edr_query_parameter_descriptions.level,
openapi_examples=custom_dimension_examples.level,
),
] = None,
method: Annotated[
str | None,
Query(
description=edr_query_parameter_descriptions.method,
openapi_examples=custom_dimension_examples.method,
),
] = None,
duration: Annotated[
str | None,
Query(
description=edr_query_parameter_descriptions.duration,
openapi_examples=custom_dimension_examples.duration,
),
] = None,
):
try:
point = wkt.loads(coords)
if point.geom_type != "Point":
raise TypeError
except GEOSException:
raise HTTPException(
status_code=400,
detail={"coords": f"Invalid or unparseable wkt provided: {coords}"},
)
except TypeError:
raise HTTPException(
status_code=400,
detail={"coords": f"Invalid geometric type: {point.geom_type}"},
)
except Exception:
raise HTTPException(
status_code=400,
detail={"coords": f"Unexpected error occurred during wkt parsing: {coords}"},
)

request = dstore.GetObsRequest(
spatial_circle=dstore.Circle(center=dstore.Point(lat=point.y, lon=point.x), radius=within),
included_response_fields=response_fields_needed_for_data_api,
)

await add_request_parameters(request, parameter_name, datetime, standard_name, level, method, duration)

grpc_response = await get_obs_request(request)
observations = grpc_response.observations
response = formatters.formatters[f](observations)

return response
Loading
Loading