Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SMHI as an weather provider for Sweden #51

Merged
merged 6 commits into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions env.sh.sample
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
# export WEATHER_MET_EIREANN=1
# Or, weather.gov self identification
# export WEATHERGOV_SELF_IDENTIFICATION=you@example.com
# Or, SMHI self identification
# export SMHI_SELF_IDENTIFICATION=you@example.com

# Your latitude and longitude to pass to weather providers
export WEATHER_LATITUDE=51.5077
Expand Down
8 changes: 7 additions & 1 deletion screen-weather-get.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys
import os
import logging
from weather_providers import climacell, openweathermap, metofficedatahub, metno, meteireann, accuweather, visualcrossing, weathergov
from weather_providers import climacell, openweathermap, metofficedatahub, metno, meteireann, accuweather, visualcrossing, weathergov, smhi
from alert_providers import metofficerssfeed, weathergovalerts
from alert_providers import meteireann as meteireannalertprovider
from utility import update_svg, configure_logging
Expand Down Expand Up @@ -37,6 +37,7 @@ def get_weather(location_lat, location_long, units):
visualcrossing_apikey = os.getenv("VISUALCROSSING_APIKEY")
use_met_eireann = os.getenv("WEATHER_MET_EIREANN")
weathergov_self_id = os.getenv("WEATHERGOV_SELF_IDENTIFICATION")
smhi_self_id = os.getenv("SMHI_SELF_IDENTIFICATION")

if (
not climacell_apikey
Expand All @@ -47,6 +48,7 @@ def get_weather(location_lat, location_long, units):
and not visualcrossing_apikey
and not use_met_eireann
and not weathergov_self_id
and not smhi_self_id
):
logging.error("No weather provider has been configured (Climacell, OpenWeatherMap, Weather.gov, MetOffice, AccuWeather, Met.no, Met Eireann, VisualCrossing...)")
sys.exit(1)
Expand Down Expand Up @@ -93,6 +95,10 @@ def get_weather(location_lat, location_long, units):
logging.info("Getting weather from Climacell")
weather_provider = climacell.Climacell(climacell_apikey, location_lat, location_long, units)

elif smhi_self_id:
logging.info("Getting weather from SMHI")
weather_provider = smhi.SMHI(smhi_self_id, location_lat, location_long, units)

weather = weather_provider.get_weather()
logging.info("weather - {}".format(weather))
return weather
Expand Down
138 changes: 138 additions & 0 deletions weather_providers/smhi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import logging
from weather_providers.base_provider import BaseWeatherProvider


class SMHI(BaseWeatherProvider):
def __init__(self, smhi_self_id, location_lat, location_long, units):
self.smhi_self_id = smhi_self_id
self.location_lat = location_lat
self.location_long = location_long
self.units = units

# Map SMHI icons to local icons
# Reference: https://opendata.smhi.se/apidocs/metfcst/parameters.html#parameter-wsymb
def get_icon_from_smhi_weathercode(self, weathercode, is_daytime):


icon_dict = {
1: "clear_sky_day" if is_daytime else "clearnight", # Clear sky
2: "clear_sky_day" if is_daytime else "clearnight", # Nearly clear sky
3: "few_clouds" if is_daytime else "partlycloudynight", # Variable cloudiness
4: "scattered_clouds" if is_daytime else "partlycloudynight", # Halfclear sky
5: "mostly_cloudy" if is_daytime else "mostly_cloudy_night", # Cloudy sky
6: "overcast", # Overcast
7: "climacell_fog", # Fog
8: 'climacell_rain_light' if is_daytime else 'rain_night_light', # Light rain showers
9: "climacell_rain" if is_daytime else "rain_night", # Moderate rain showers
10: "climacell_rain_heavy" if is_daytime else "rain_night_heavy", # Heavy rain showers
11: "thundershower_rain", # Thunderstorm
12: "sleet", # Light sleet showers
13: "sleet", # Moderate sleet showers
14: "sleet", # Heavy sleet showers
15: "climacell_snow_light", # Light snow showers
16: "snow", # Moderate snow showers
17: "snow", # Heavy snow showers
18: 'climacell_rain_light' if is_daytime else 'rain_night_light', # Light rain
19: "climacell_rain" if is_daytime else "rain_night", # Moderate rain
20: "climacell_rain_heavy" if is_daytime else "rain_night_heavy", # Heavy rain
21: "thundershower_rain", # Thunder
22: "sleet", # Light sleet
23: "sleet", # Moderate sleet
24: "sleet", # Heavy sleet
25: "climacell_snow_light", # Light snowfall
26: "snow", # Moderate snowfall
27: "snow", # Heavy snowfall
}

icon = icon_dict[weathercode]
logging.debug(
"get_icon_by_weathercode({}) - {}"
.format(weathercode, icon))

return icon

def get_description_from_smhi_weathercode(self, weathercode):
description_dict = {
1: "Clear sky",
2: "Nearly clear sky",
3: "Variable cloudiness",
4: "Halfclear sky",
5: "Cloudy sky",
6: "Overcast",
7: "Fog",
8: "Light rain showers",
9: "Moderate rain showers",
10: "Heavy rain showers",
11: "Thunderstorm",
12: "Light sleet showers",
13: "Moderate sleet showers",
14: "Heavy sleet showers",
15: "Light snow showers",
16: "Moderate snow showers",
17: "Heavy snow showers",
18: "Light rain",
19: "Moderate rain",
20: "Heavy rain",
21: "Thunder",
22: "Light sleet",
23: "Moderate sleet",
24: "Heavy sleet",
25: "Light snowfall",
26: "Moderate snowfall",
27: "Heavy snowfall",
}
description = description_dict[weathercode]

logging.debug(
"get_description_by_weathercode({}) - {}"
.format(weathercode, description))

return description.title()

# Get weather from SMHI API
# https://opendata.smhi.se/apidocs/metfcst/get-forecast.html#get-point-forecast
# The API response is a complete forecast approximately 10 days ahead of the latest current forecast.
# All times in the answer given in UTC.
# Precipitation parameters have a distribution in time (a time interval) until the valid time for current data.
# The interval starts at the time step before. At the beginning of the forecast, the interval is one hour.
# Later in the forecast, the time interval increases (eg 3, 6 and 12 h). Unit remains mm / h.
# So, current hour is index 0, next hour is index 1
# The API accepts 6 decimals in the lon/lat. With more decimals, it returns 404.

def get_weather(self):

url = ("https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/{}/lat/{}/data.json"
.format(self.location_long, self.location_lat))

headers = {"User-Agent": self.smhi_self_id}

response_data = self.get_response_json(url, headers=headers)
logging.debug(response_data)
weather_data = response_data["timeSeries"]
logging.debug("get_weather() - {}".format(weather_data))

# Get the weather code of the first item.
for data in weather_data[0]["parameters"]:
if data["name"] == "Wsymb2":
weather_code = data["values"][0]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my testing the weather symbol was sometimes appearing in a different location so I decided just keep the looping bit to find the Wsymb2.


daytime = self.is_daytime(self.location_lat, self.location_long)

# { "temperatureMin": "2.0", "temperatureMax": "15.1", "icon": "mostly_cloudy", "description": "Cloudy with light breezes" }
# No Min or Max here. We just get the estimated temperature for the hour.
# Since we get the forecast for several hours and days, get the min/max for the next 12 hours?
weather = {}
temp_list = []

for item in range(0, 12):
for param in weather_data[item]['parameters']:
if param['name'] == 't':
temp = param['values'][0]
temp_list.append(temp)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looping through 12 forecasts, getting the temperature out, adding it to a list, then below getting min and max of those temperatures. So it acts as a min/max forecast for 12 hours.

The weather symbol unfortunately that's a bit difficult since there's no "min/max" or "average" of symbols.


weather["temperatureMin"] = min(temp_list)
weather["temperatureMax"] = max(temp_list)
weather["icon"] = self.get_icon_from_smhi_weathercode(weather_code, daytime)
weather["description"] = self.get_description_from_smhi_weathercode(weather_code)
logging.debug(weather)
return weather