Skip to content

Commit

Permalink
Add time range selectio to API.
Browse files Browse the repository at this point in the history
  • Loading branch information
lukas-phaf committed Nov 2, 2023
1 parent d10d72c commit 8596f08
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 7 deletions.
51 changes: 45 additions & 6 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
# For developing: uvicorn main:app --reload
import math
import os
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from itertools import groupby
from typing import Tuple

import datastore_pb2 as dstore
import datastore_pb2_grpc as dstore_grpc
Expand All @@ -26,17 +29,21 @@
from edr_pydantic.collections import Collection
from edr_pydantic.collections import Collections
from fastapi import FastAPI
from fastapi import HTTPException
from fastapi import Path
from fastapi import Query
from fastapi.requests import Request
from geojson_pydantic import Feature
from geojson_pydantic import FeatureCollection
from geojson_pydantic import Point
from google.protobuf.timestamp_pb2 import Timestamp
from pydantic import AwareDatetime
from pydantic import TypeAdapter
from shapely import buffer
from shapely import geometry
from shapely import wkt


app = FastAPI()
app.add_middleware(BrotliMiddleware)

Expand Down Expand Up @@ -104,14 +111,41 @@ def get_data_for_time_series(get_obs_request):

coverages.append(Coverage(domain=domain, parameters=parameters, ranges=ranges))

if len(coverages) == 1:
if len(coverages) == 0:
raise HTTPException(status_code=404, detail="No data found")
elif len(coverages) == 1:
return coverages[0]
else:
return CoverageCollection(
coverages=coverages, parameters=coverages[0].parameters
) # HACK to take parameters from first one


def get_datetime_range(datetime_string: str | None) -> Tuple[Timestamp, Timestamp] | None:
if not datetime_string:
return None

start_datetime, end_datetime = Timestamp(), Timestamp()
aware_datetime_type_adapter = TypeAdapter(AwareDatetime)
datetimes = tuple(value.strip() for value in datetime_string.split("/"))
if len(datetimes) == 1:
start_datetime.FromDatetime(aware_datetime_type_adapter.validate_python(datetimes[0]))
end_datetime.FromDatetime(
aware_datetime_type_adapter.validate_python(datetimes[0]) + timedelta(seconds=1)
) # HACK: Add one second so we get some data, as the store returns [start, end)
else:
if datetimes[0] != "..":
start_datetime.FromDatetime(aware_datetime_type_adapter.validate_python(datetimes[0]))
else:
start_datetime.FromDatetime(datetime.min)
if datetimes[1] != "..":
end_datetime.FromDatetime(aware_datetime_type_adapter.validate_python(datetimes[1]))
else:
end_datetime.FromDatetime(datetime.max)

return start_datetime, end_datetime


@app.get(
"/",
tags=["Capabilities"],
Expand Down Expand Up @@ -185,14 +219,15 @@ def get_locations(bbox: str = Query(..., example="5.0,52.0,6.0,52.1")) -> Featur
def get_data_location_id(
location_id: str = Path(..., example="06260"),
parameter_name: str = Query(..., alias="parameter-name", example="dd,ff,rh,pp,tn"),
datetime: str | None = None,
):
# TODO: There is no error handling of any kind at the moment!
# This is just a quick and dirty demo
# TODO: Get time interval from request (example to create protobuf timestamp:
# from_time = Timestamp()
# from_time.FromDatetime(datetime(2022, 12, 31))
range = get_datetime_range(datetime)
get_obs_request = dstore.GetObsRequest(
platforms=[location_id], instruments=list(map(str.strip, parameter_name.split(",")))
platforms=[location_id],
instruments=list(map(str.strip, parameter_name.split(","))),
interval=dstore.TimeInterval(start=range[0], end=range[1]) if range else None,
)
return get_data_for_time_series(get_obs_request)

Expand All @@ -206,11 +241,12 @@ def get_data_location_id(
def get_data_position(
coords: str = Query(..., example="POINT(5.179705 52.0988218)"),
parameter_name: str = Query(..., alias="parameter-name", example="dd,ff,rh,pp,tn"),
datetime: str | None = None,
):
point = wkt.loads(coords)
assert point.geom_type == "Point"
poly = buffer(point, 0.0001, quad_segs=1) # Roughly 10 meters around the point
return get_data_area(poly.wkt, parameter_name)
return get_data_area(poly.wkt, parameter_name, datetime)


@app.get(
Expand All @@ -222,11 +258,14 @@ def get_data_position(
def get_data_area(
coords: str = Query(..., example="POLYGON((5.0 52.0, 6.0 52.0,6.0 52.1,5.0 52.1, 5.0 52.0))"),
parameter_name: str = Query(..., alias="parameter-name", example="dd,ff,rh,pp,tn"),
datetime: str | None = None,
):
poly = wkt.loads(coords)
assert poly.geom_type == "Polygon"
range = get_datetime_range(datetime)
get_obs_request = dstore.GetObsRequest(
instruments=list(map(str.strip, parameter_name.split(","))),
inside=dstore.Polygon(points=[dstore.Point(lat=coord[1], lon=coord[0]) for coord in poly.exterior.coords]),
interval=dstore.TimeInterval(start=range[0], end=range[1]) if range else None,
)
return get_data_for_time_series(get_obs_request)
19 changes: 18 additions & 1 deletion integration-test/test_knmi.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Note that this assumes that the KNMI test data is loader (using loader container)
import os
from datetime import datetime

import datastore_pb2 as dstore
import datastore_pb2_grpc as dstore_grpc
import grpc
import pytest
from google.protobuf.timestamp_pb2 import Timestamp


NUMBER_OF_PARAMETERS = 44
Expand Down Expand Up @@ -41,7 +43,7 @@ def test_find_series_single_station_all_parameters(grpc_stub):
assert len(response.observations) == NUMBER_OF_PARAMETERS


def test_get_values_single_station_single_parameters(grpc_stub):
def test_get_values_single_station_single_parameter(grpc_stub):
ts_request = dstore.GetObsRequest(platforms=["06260"], instruments=["rh"])
response = grpc_stub.GetObservations(ts_request)

Expand All @@ -52,6 +54,21 @@ def test_get_values_single_station_single_parameters(grpc_stub):
assert float(observations[-1].value) == 59.0


def test_get_values_single_station_single_parameter_one_hour(grpc_stub):
start_datetime, end_datetime = Timestamp(), Timestamp()
start_datetime.FromDatetime(datetime(2022, 12, 31, 11))
end_datetime.FromDatetime(datetime(2022, 12, 31, 12))

ts_request = dstore.GetObsRequest(
platforms=["06260"], instruments=["rh"], interval=dstore.TimeInterval(start=start_datetime, end=end_datetime)
)
response = grpc_stub.GetObservations(ts_request)

assert len(response.observations) == 1
observations = response.observations[0].obs_mdata
assert len(observations) == 6


input_params_polygon = [
(
# Multiple stations within
Expand Down

0 comments on commit 8596f08

Please sign in to comment.