-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
190 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
# | ||
# @Author: José Sánchez-Gallego ([email protected]) | ||
# @Date: 2024-03-26 | ||
# @Filename: weather.py | ||
# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) | ||
|
||
from __future__ import annotations | ||
|
||
import polars | ||
from fastapi import APIRouter | ||
|
||
from lvmapi.tools.weather import get_weather_data | ||
|
||
|
||
router = APIRouter(prefix="/weather", tags=["weather"]) | ||
|
||
|
||
@router.get("/") | ||
@router.get("/summary") | ||
async def get_weather(station: str = "DuPont") -> list[dict]: | ||
"""Returns the weather summary (last 60 minutes) from a weather station.""" | ||
|
||
df = await get_weather_data(station=station) | ||
|
||
return df.with_columns(ts=polars.col.ts.dt.to_string("%FT%X")).to_dicts() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
# | ||
# @Author: José Sánchez-Gallego ([email protected]) | ||
# @Date: 2024-03-26 | ||
# @Filename: weather.py | ||
# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) | ||
|
||
from __future__ import annotations | ||
|
||
import httpx | ||
import polars | ||
from astropy.time import Time, TimeDelta | ||
|
||
|
||
__all__ = ['get_weather_data'] | ||
|
||
|
||
WEATHER_URL = "http://dataservice.lco.cl/vaisala/data" | ||
|
||
|
||
async def get_weather_data( | ||
end_time: str | None = None, | ||
start_time: str | float = 3600, | ||
station="DuPont", | ||
): | ||
"""Returns weather data from the Vaisala weather station. | ||
Parameters | ||
---------- | ||
end_time | ||
The end time for the query. If not provided, the current time is used. | ||
The time can be an Astropy Time object or a string in ISO format. | ||
start_time | ||
The start time for the query. The time can be an Astropy Time object, | ||
a string in ISO format, or a float indicating with a time delta in seconds | ||
with respect to ``end_time``. | ||
Returns | ||
------- | ||
dataframe | ||
A Polars dataframe with the time series weather data. | ||
""" | ||
|
||
if station not in ["DuPont", "C40", "Magellan"]: | ||
raise ValueError("station must be one of 'DuPont', 'C40', or 'Magellan'.") | ||
|
||
if end_time is None: | ||
end_time_ap = Time.now() | ||
elif isinstance(end_time, str): | ||
end_time_ap = Time(end_time) | ||
elif isinstance(end_time, Time): | ||
end_time_ap = end_time | ||
else: | ||
raise ValueError("end_time must be a string or an Astropy Time object.") | ||
|
||
if isinstance(start_time, str): | ||
start_time_ap = Time(start_time) | ||
elif isinstance(start_time, (int, float)): | ||
start_time_ap = end_time_ap - TimeDelta(start_time, format="sec") | ||
elif isinstance(start_time, Time): | ||
start_time_ap = start_time | ||
else: | ||
raise ValueError( | ||
"start_time must be a string, a time delta in seconds, " | ||
"or an Astropy Time object." | ||
) | ||
|
||
end_time_ap.precision = 0 | ||
start_time_ap.precision = 0 | ||
|
||
async with httpx.AsyncClient() as client: | ||
response = await client.get( | ||
WEATHER_URL, | ||
params={ | ||
"start_ts": str(start_time_ap.iso), | ||
"end_ts": str(end_time_ap.iso), | ||
"station": station, | ||
}, | ||
) | ||
|
||
if response.status_code != 200: | ||
raise ValueError(f"Failed to get weather data: {response.text}") | ||
|
||
data = response.json() | ||
|
||
if "Error" in data: | ||
raise ValueError(f"Failed to get weather data: {data['Error']}") | ||
elif "results" not in data or data["results"] is None: | ||
raise ValueError("Failed to get weather data: no results found.") | ||
|
||
results = data["results"] | ||
|
||
df = polars.DataFrame(results) | ||
df = df.with_columns( | ||
ts=polars.col("ts").str.to_datetime(time_unit="ms"), | ||
station=polars.lit(station, polars.String), | ||
) | ||
|
||
# Delete rows with all null values. | ||
df = df.filter(~polars.all_horizontal(polars.exclude("ts").is_null())) | ||
|
||
# Sort by timestamp | ||
df = df.sort("ts") | ||
|
||
return df |