Skip to content

Commit

Permalink
Add weather endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
albireox committed Mar 26, 2024
1 parent f467d83 commit 502be62
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 2 deletions.
47 changes: 46 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ astropy = "^6.0.0"
astroplan = "^0.9.1"
polars = "^0.20.16"
redis = {version = "^5.0.3", extras = ["hiredis"]}
httpx = "^0.27.0"

[tool.poetry.group.dev.dependencies]
ipython = ">=8.0.0"
Expand Down
10 changes: 9 additions & 1 deletion src/lvmapi/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@
from fastapi import FastAPI

from lvmapi import auth
from lvmapi.routers import ephemeris, overwatcher, slack, spectrographs, telescopes
from lvmapi.routers import (
ephemeris,
overwatcher,
slack,
spectrographs,
telescopes,
weather,
)


app = FastAPI()
Expand All @@ -21,6 +28,7 @@
app.include_router(slack.router)
app.include_router(ephemeris.router)
app.include_router(overwatcher.router)
app.include_router(weather.router)


@app.get("/")
Expand Down
27 changes: 27 additions & 0 deletions src/lvmapi/routers/weather.py
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()
107 changes: 107 additions & 0 deletions src/lvmapi/tools/weather.py
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

0 comments on commit 502be62

Please sign in to comment.