mirror of
https://github.com/jdejaegh/python-irceline.git
synced 2025-06-27 03:35:56 +02:00
Handle daily mean values from WFS as well
This commit is contained in:
parent
7248caed49
commit
c723d409e5
4 changed files with 300 additions and 16 deletions
|
@ -1,6 +1,6 @@
|
|||
import asyncio
|
||||
import socket
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, date
|
||||
from typing import Tuple, List, Dict
|
||||
|
||||
import aiohttp
|
||||
|
@ -40,28 +40,34 @@ class IrcelineClient:
|
|||
return round(result[0]), round(result[1])
|
||||
|
||||
async def get_rio_value(self,
|
||||
timestamp: datetime,
|
||||
timestamp: datetime | date,
|
||||
features: List[RioFeature],
|
||||
position: Tuple[float, float]
|
||||
) -> Dict[RioFeature, FeatureValue]:
|
||||
"""
|
||||
Call the WFS API to get the interpolated level of RioFeature
|
||||
Call the WFS API to get the interpolated level of RioFeature. Raises exception upon API error
|
||||
:param timestamp: datetime for which to get the data for
|
||||
:param features: list of RioFeature to fetch from the API
|
||||
:param position: decimal degrees pair of coordinates
|
||||
:return: dict with the response (key is RioFeature, value is FeatureValue with actual value and timestamp)
|
||||
"""
|
||||
|
||||
# Remove one hour from timestamp to handle case where the hour just passed but the data is not yet there
|
||||
# Remove one hour/day from timestamp to handle case where the hour just passed but the data is not yet there
|
||||
# (e.g. 5.01 PM, but the most recent data is for 4.00 PM)
|
||||
if isinstance(timestamp, datetime):
|
||||
timestamp = timestamp.replace(microsecond=0, second=0, minute=0) - timedelta(hours=1)
|
||||
elif isinstance(timestamp, date):
|
||||
timestamp = timestamp - timedelta(days=1)
|
||||
|
||||
coord = self.epsg_transform(position)
|
||||
querystring = {"service": "WFS",
|
||||
"version": "1.3.0",
|
||||
"request": "GetFeature",
|
||||
"outputFormat": "application/json",
|
||||
"typeName": ",".join(features),
|
||||
"cql_filter": f"timestamp>='{timestamp.isoformat()}' AND "
|
||||
"cql_filter":
|
||||
f"{'timestamp' if isinstance(timestamp, datetime) else 'date'}>='{timestamp.isoformat()}'"
|
||||
f" AND "
|
||||
f"INTERSECTS(the_geom, POINT ({coord[0]} {coord[1]}))"}
|
||||
|
||||
r: ClientResponse = await self._api_wrapper(rio_wfs_base_url, querystring)
|
||||
|
@ -82,14 +88,21 @@ class IrcelineClient:
|
|||
features_api = data.get('features', [])
|
||||
result = dict()
|
||||
for f in features_api:
|
||||
props = f.get('properties', {})
|
||||
if (f.get('id', None) is None or
|
||||
f.get('properties', {}).get('value', None) is None or
|
||||
f.get('properties', {}).get('timestamp', None) is None):
|
||||
props.get('value', None) is None):
|
||||
continue
|
||||
if (props.get('timestamp', None) is None and
|
||||
props.get('date', None) is None):
|
||||
continue
|
||||
|
||||
try:
|
||||
timestamp = datetime.fromisoformat(f.get('properties', {}).get('timestamp'))
|
||||
value = float(f.get('properties', {}).get('value'))
|
||||
if 'timestamp' in props.keys():
|
||||
timestamp = datetime.fromisoformat(props.get('timestamp'))
|
||||
else:
|
||||
# Cut last character as the date is written '2024-06-15Z' which is not ISO compliant
|
||||
timestamp = date.fromisoformat(props.get('date')[:-1])
|
||||
value = float(props.get('value'))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from enum import StrEnum
|
||||
from typing import TypedDict
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date
|
||||
|
||||
|
||||
class RioFeature(StrEnum):
|
||||
|
@ -23,5 +23,5 @@ class RioFeature(StrEnum):
|
|||
|
||||
|
||||
class FeatureValue(TypedDict):
|
||||
timestamp: datetime | None
|
||||
timestamp: datetime | date
|
||||
value: int | float | None
|
||||
|
|
243
tests/fixtures/rio_wfs_dmean.json
vendored
Normal file
243
tests/fixtures/rio_wfs_dmean.json
vendored
Normal file
|
@ -0,0 +1,243 @@
|
|||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"id": "bc_dmean.fid-280be381_1901d555556_494e",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"geometry_name": "the_geom",
|
||||
"properties": {
|
||||
"id": 1102,
|
||||
"date": "2024-06-14Z",
|
||||
"value": 0.2,
|
||||
"network": "Wallonia"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"id": "bc_dmean.fid-280be381_1901d555556_494f",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"geometry_name": "the_geom",
|
||||
"properties": {
|
||||
"id": 1102,
|
||||
"date": "2024-06-15Z",
|
||||
"value": 0.1,
|
||||
"network": "Wallonia"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"id": "pm10_dmean.fid-280be381_1901d555556_4950",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"geometry_name": "the_geom",
|
||||
"properties": {
|
||||
"id": 1102,
|
||||
"date": "2024-06-14Z",
|
||||
"value": 4.8,
|
||||
"network": "Wallonia"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"id": "pm10_dmean.fid-280be381_1901d555556_4951",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"geometry_name": "the_geom",
|
||||
"properties": {
|
||||
"id": 1102,
|
||||
"date": "2024-06-15Z",
|
||||
"value": 5.9,
|
||||
"network": "Wallonia"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"id": "pm25_dmean.fid-280be381_1901d555556_4952",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"geometry_name": "the_geom",
|
||||
"properties": {
|
||||
"id": 1102,
|
||||
"date": "2024-06-14Z",
|
||||
"value": 1.3,
|
||||
"network": "Wallonia"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"id": "pm25_dmean.fid-280be381_1901d555556_4953",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
132000
|
||||
],
|
||||
[
|
||||
186000,
|
||||
128000
|
||||
],
|
||||
[
|
||||
182000,
|
||||
128000
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"geometry_name": "the_geom",
|
||||
"properties": {
|
||||
"id": 1102,
|
||||
"date": "2024-06-15Z",
|
||||
"value": 1.1,
|
||||
"network": "Wallonia"
|
||||
}
|
||||
}
|
||||
],
|
||||
"totalFeatures": 6,
|
||||
"numberMatched": 6,
|
||||
"numberReturned": 6,
|
||||
"timeStamp": "2024-06-15T19:30:09.581Z",
|
||||
"crs": {
|
||||
"type": "name",
|
||||
"properties": {
|
||||
"name": "urn:ogc:def:crs:EPSG::31370"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
from datetime import datetime, date
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from freezegun import freeze_time
|
||||
|
||||
from src.open_irceline.api import IrcelineClient
|
||||
from src.open_irceline.data import RioFeature, FeatureValue
|
||||
from tests.conftest import get_api_data
|
||||
from datetime import datetime
|
||||
from freezegun import freeze_time
|
||||
|
||||
|
||||
@freeze_time(datetime.fromisoformat("2024-06-15T16:55:03.419Z"))
|
||||
async def test_format_result():
|
||||
async def test_format_result_hmean():
|
||||
data = get_api_data('rio_wfs.json')
|
||||
result = IrcelineClient.format_result('rio', data, [RioFeature.NO2_HMEAN, RioFeature.O3_HMEAN])
|
||||
|
||||
|
@ -22,3 +25,28 @@ async def test_format_result():
|
|||
}
|
||||
|
||||
assert result == expected
|
||||
|
||||
|
||||
@freeze_time(datetime.fromisoformat("2024-06-15T19:30:09.581Z"))
|
||||
|
||||
async def test_format_result_dmean():
|
||||
data = get_api_data('rio_wfs_dmean.json')
|
||||
result = IrcelineClient.format_result('rio', data,
|
||||
[RioFeature.BC_DMEAN, RioFeature.PM10_DMEAN, RioFeature.PM25_DMEAN])
|
||||
|
||||
expected = {
|
||||
str(RioFeature.BC_DMEAN): FeatureValue(timestamp=date(2024, 6, 15), value=0.1),
|
||||
str(RioFeature.PM10_DMEAN): FeatureValue(timestamp=date(2024, 6, 15), value=5.9),
|
||||
str(RioFeature.PM25_DMEAN): FeatureValue(timestamp=date(2024, 6, 15), value=1.1),
|
||||
}
|
||||
|
||||
assert result == expected
|
||||
|
||||
async def test_run():
|
||||
async with ClientSession() as session:
|
||||
api = IrcelineClient(session)
|
||||
r = await api.get_rio_value(
|
||||
date.today(),
|
||||
[RioFeature.BC_DMEAN, RioFeature.PM10_DMEAN, RioFeature.PM25_DMEAN],
|
||||
(4.8637, 50.4656))
|
||||
print(r)
|
||||
|
|
Loading…
Add table
Reference in a new issue