FitTrackee/fittrackee/workouts/utils/weather.py

130 lines
4.5 KiB
Python
Raw Normal View History

import os
2022-11-07 23:02:32 +01:00
from datetime import datetime, timedelta
from typing import Dict, Optional, Union
import forecastio
import pytz
2022-11-07 23:02:32 +01:00
import requests
2022-06-11 13:10:02 +02:00
from gpxpy.gpx import GPXTrackPoint
2021-01-20 16:47:00 +01:00
from fittrackee import appLog
2020-09-19 13:56:14 +02:00
API_KEY = os.getenv('WEATHER_API_KEY')
VC_API_KEY = os.getenv('VC_WEATHER_API_KEY')
2022-06-11 13:10:02 +02:00
def get_weather(point: GPXTrackPoint) -> Optional[Dict]:
try:
if not point.time:
2022-11-07 23:02:32 +01:00
# if there's no time associated with the point;
# we cannot get weather
return None
else:
point_time = (
pytz.utc.localize(point.time)
if point.time.tzinfo is None
else point.time.astimezone(pytz.utc)
)
if API_KEY:
# if darksky api key is present, use that
forecast = forecastio.load_forecast(
API_KEY,
point.latitude,
point.longitude,
time=point_time,
units='si',
)
weather = forecast.currently()
return {
'summary': weather.summary,
'icon': weather.icon,
'temperature': weather.temperature,
'humidity': weather.humidity,
'wind': weather.windSpeed,
'windBearing': weather.windBearing,
}
elif VC_API_KEY:
# if visualcrossing API key is present, use that
2022-11-07 23:02:32 +01:00
return get_visual_crossing_weather(
VC_API_KEY, point.latitude, point.longitude, point.time
)
else:
return None
except Exception as e:
appLog.error(e)
return None
2022-11-07 23:02:32 +01:00
def get_visual_crossing_weather(
api_key: str, latitude: float, longitude: float, time: datetime
) -> Dict[str, Union[str, float]]:
# All requests to the Timeline Weather API use the following the form:
# https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services
2022-11-07 23:02:32 +01:00
# /timeline/[location]/[date1]/[date2]?key=YOUR_API_KEY
2022-11-07 23:02:32 +01:00
# location (required) is the address, partial address or
# latitude,longitude location for
# which to retrieve weather data. You can also use US ZIP Codes.
2022-11-07 23:02:32 +01:00
# date1 (optional) is the start date for which to retrieve weather data.
# You can also request the information for a specific time for a single
# date by including time into the date1 field using the format
# yyyy-MM-ddTHH:mm:ss. For example 2020-10-19T13:00:00.
# The results are returned in the currentConditions field and are
# truncated to the hour requested (i.e. 2020-10-19T13:59:00 will return
# data at 2020-10-19T13:00:00).
2022-11-07 23:02:32 +01:00
# first, round datetime to nearest hour by truncating, and then adding an
# hour if the "real" time's number of minutes is 30 or more (we do this
# since the API only truncates)
trunc_time = time.replace(
second=0, microsecond=0, minute=0, hour=time.hour
) + timedelta(hours=time.minute // 30)
appLog.debug(
f'VC_weather: truncated time {time} ({time.timestamp()}) to '
f'{trunc_time} ({trunc_time.timestamp()})'
)
2022-11-07 23:02:32 +01:00
base_url = (
'https://weather.visualcrossing.com/'
+ 'VisualCrossingWebServices/rest/services'
)
url = (
f"{base_url}/timeline/{latitude},{longitude}"
f"/{int(trunc_time.timestamp())}?key={api_key}"
)
params = {
2022-11-07 23:02:32 +01:00
"unitGroup": "metric",
"contentType": "json",
"elements": (
"datetime,datetimeEpoch,temp,humidity,windspeed,"
"winddir,conditions,description,icon"
),
"include": "current",
}
2022-11-07 23:02:32 +01:00
appLog.debug(
f'VC_weather: getting weather from {url}'.replace(api_key, '*****')
)
r = requests.get(url, params=params)
r.raise_for_status()
res = r.json()
weather = res['currentConditions']
# FitTrackee expects the following units:
2022-11-07 23:02:32 +01:00
# temp: Celsius,
# humidity: in fraction (rather than percent)
# windSpeed: m/s
# windBearing: direction wind is from in degrees (0 is north)
# VC provides humidity in percent, wind in km/h
2022-11-07 23:02:32 +01:00
data = {
'summary': weather['conditions'],
'icon': f"vc-{weather['icon']}",
'temperature': weather['temp'],
'humidity': weather['humidity'] / 100,
2022-11-07 23:02:32 +01:00
'wind': weather['windspeed'] * 1000 / (60 * 60), # km/h to m/s
'windBearing': weather['winddir'],
}
return data