diff --git a/gallery/easel/route/view/weather/filters.py b/gallery/easel/route/view/weather/filters.py index 84f311a..e4d8c20 100644 --- a/gallery/easel/route/view/weather/filters.py +++ b/gallery/easel/route/view/weather/filters.py @@ -7,17 +7,15 @@ from gallery.sketch.weather.model import ( Precipitation, Sky, WindDirection, - WindDirectionDeg, ) from gallery.util import root_path -def wind_direction_icon(wind_direction_deg: float) -> str: - wind_direction = WindDirectionDeg(wind_direction_deg).direction - if wind_direction == WindDirection.CALM: +def wind_direction_icon(wind_direction: WindDirection | None) -> str: + if wind_direction is None: return "wind-calm" else: - return f"wind-from-{wind_direction.name.lower()}" + return f"wind-from-{wind_direction.lower()}" def cloudness_icon(sky: Sky, date: datetime.datetime, period: str) -> list[str]: diff --git a/gallery/easel/route/view/weather/templates/weather.html b/gallery/easel/route/view/weather/templates/weather.html index b7d8db5..be9b73b 100644 --- a/gallery/easel/route/view/weather/templates/weather.html +++ b/gallery/easel/route/view/weather/templates/weather.html @@ -107,8 +107,8 @@ locale=request.state.language)}}{% endblock %} {% for value in response.values %} -
+
{{ 'wind-deg' | weather_icon_svg }}
{% endfor %} @@ -123,11 +123,11 @@ locale=request.state.language)}}{% endblock %} {% for value in response.values %} - {{value.wind_speed}} - {% if value.wind_gust != value.wind_speed and value.wind_gust > 0 %} + style="background-color: rgba(128, 128, 128, {{value.wind.speed * 0.05}});"> + {{value.wind.speed}} + {% if value.wind.gust != value.wind.speed and value.wind.gust > 0 %} - ({{value.wind_gust}}) + ({{value.wind.gust}}) {% endif %} diff --git a/gallery/painting/gismeteo/api.py b/gallery/painting/gismeteo/api.py index 88973f2..327fbaf 100644 --- a/gallery/painting/gismeteo/api.py +++ b/gallery/painting/gismeteo/api.py @@ -28,6 +28,15 @@ def format_date(date: datetime.date) -> str: return f"{days + 1}-day" +def set_dict_nested_value(target: dict, key: str, value: Any): + parts = key.split(".") + for part in parts[:-1]: + if part not in target: + target[part] = {} + target = target[part] + target[parts[-1]] = value + + class GismeteoApi(WeatherApi): PROVIDER = "gismeteo" @@ -46,7 +55,7 @@ class GismeteoApi(WeatherApi): for index, value in enumerate(parser.parse_row(widget)): while len(result) < index + 1: result.append({}) - result[index][parser.KEY] = value + set_dict_nested_value(result[index], parser.KEY, value) values = [WeatherValue(**item) for item in result] # group values by 3 hours if len(values) > 8: @@ -70,7 +79,7 @@ class GismeteoApi(WeatherApi): for index, value in enumerate(parser.parse_row(widget)): while len(result) < index + 1: result.append({}) - result[index][parser.KEY] = value + set_dict_nested_value(result[index], parser.KEY, value) values = [WeatherValue(**item) for item in result] return WeatherResponse( location=location or "n/a", diff --git a/gallery/painting/gismeteo/parser.py b/gallery/painting/gismeteo/parser.py index 56f5d24..edc054c 100644 --- a/gallery/painting/gismeteo/parser.py +++ b/gallery/painting/gismeteo/parser.py @@ -161,7 +161,7 @@ class TemperatureParser(RowParser[list[int]]): class WindSpeedParser(RowParser[int]): - KEY = "wind_speed" + KEY = "wind.speed" def parse_row(self, tag: Tag) -> Iterable[int]: for item in tag.select(".widget-row-wind > .row-item > .wind-speed > speed-value"): @@ -169,7 +169,7 @@ class WindSpeedParser(RowParser[int]): class WindGustParser(RowParser[int]): - KEY = "wind_gust" + KEY = "wind.gust" def parse_row(self, tag: Tag) -> Iterable[int]: for item in tag.select(".widget-row-wind > .row-item > .wind-gust"): @@ -178,7 +178,7 @@ class WindGustParser(RowParser[int]): class WindDirectionParser(RowParser[WindDirection]): - KEY = "wind_direction" + KEY = "wind.direction" def parse_row(self, tag: Tag) -> Iterable[float]: for item in tag.select(".widget-row-wind > .row-item > .wind-speed > .wind-direction"): diff --git a/gallery/painting/openweather/parser.py b/gallery/painting/openweather/parser.py index 5211d32..6c29049 100644 --- a/gallery/painting/openweather/parser.py +++ b/gallery/painting/openweather/parser.py @@ -3,6 +3,7 @@ import datetime from gallery.sketch.weather.model import Cloudness, Precipitation, WeatherValue from gallery.sketch.weather.util import build_weather_value +from ...sketch.weather.parser import parse_wind_direction from .openweather import ForecastItem @@ -31,9 +32,9 @@ class ForecastItemParser: # value.temperature = [round(item.main.temp_max), round(item.main.temp_min)] value.pressure = [round(item.main.pressure * 0.75006)] value.humidity = item.main.humidity - value.wind_speed = round(item.wind.speed) - value.wind_gust = round(item.wind.gust) - value.wind_direction = item.wind.deg + value.wind.speed = round(item.wind.speed) + value.wind.gust = round(item.wind.gust) + value.wind.direction = parse_wind_direction(item.wind.deg) value.sky.cloudness = self.CLOUDNESS_MAP.get(item.weather[0].description, Cloudness.CLEAR) value.sky.precipitation = self.PRECIPITATION_MAP.get(item.weather[0].description, Precipitation.NO) if item.rain: diff --git a/gallery/painting/rp5/parser.py b/gallery/painting/rp5/parser.py index 8546330..718c617 100644 --- a/gallery/painting/rp5/parser.py +++ b/gallery/painting/rp5/parser.py @@ -133,13 +133,13 @@ def parse_page(page: BeautifulSoup) -> list[WeatherResponse]: values[index].pressure = [int(cell.select_one(".p_0").text.strip())] case RowType.WIND_SPEED: for index, cell in enumerate(row.select("td")[1:]): - values[index].wind_speed = _parse_wind_speed(cell) + values[index].wind.speed = _parse_wind_speed(cell) case RowType.WIND_GUST: for index, cell in enumerate(row.select("td")[1:]): - values[index].wind_gust = _parse_wind_speed(cell) + values[index].wind.gust = _parse_wind_speed(cell) case RowType.WIND_DIRECTION: for index, cell in enumerate(row.select("td")[1:]): - values[index].wind_direction = parse_wind_direction(cell.text.strip()) + values[index].wind.direction = parse_wind_direction(cell.text.strip()) case RowType.HUMIDITY: for index, cell in enumerate(row.select("td")[1:]): values[index].humidity = int(cell.text.strip()) diff --git a/gallery/painting/yandexweather/parser.py b/gallery/painting/yandexweather/parser.py index 1f98a4b..300d4e1 100644 --- a/gallery/painting/yandexweather/parser.py +++ b/gallery/painting/yandexweather/parser.py @@ -72,10 +72,10 @@ def parse_article(article: Tag, location: str) -> WeatherResponse: value.temperature.append(temperature) wind_speed_tag = temperature_tag.next_element.next_element wind_speed = int(wind_speed_tag.text.strip()) - value.wind_speed = wind_speed + value.wind.speed = wind_speed wind_direction_tag = wind_speed_tag.next_element.next_element.next_element.next_element wind_direction_str = wind_direction_tag.text.strip() - value.wind_direction = parse_wind_direction(wind_direction_str) + value.wind.direction = parse_wind_direction(wind_direction_str) humidity_tag = wind_direction_tag.next_element.next_element.next_element.next_element humidity = int(humidity_tag.text.strip().rstrip("%")) value.humidity = humidity diff --git a/gallery/sketch/weather/model.py b/gallery/sketch/weather/model.py index 59dc7a9..31f08c9 100644 --- a/gallery/sketch/weather/model.py +++ b/gallery/sketch/weather/model.py @@ -58,7 +58,6 @@ class Sky(Model): class WindDirection(StrEnum): - CALM = auto() N = auto() NE = auto() E = auto() @@ -69,62 +68,17 @@ class WindDirection(StrEnum): NW = auto() -class WindDirectionDeg(float): - @property - def direction(self) -> WindDirection: - return self.to_direction() - - @property - def value(self) -> float: - return self - - # pylint:disable=too-many-return-statements - def to_direction(self) -> WindDirection: - if self == -1: - return WindDirection.CALM - elif self > 337.5 or self <= 22.25: - return WindDirection.N - elif self <= 67.5: - return WindDirection.NE - elif self <= 112.5: - return WindDirection.E - elif self <= 157.5: - return WindDirection.SE - elif self <= 202.5: - return WindDirection.S - elif self <= 247.5: - return WindDirection.SW - elif self <= 292.5: - return WindDirection.W - elif self <= 337.5: - return WindDirection.NW - else: - raise ValueError(self) - - @classmethod - def from_direction(cls, direction: WindDirection) -> Self: - return cls( - { - WindDirection.CALM: -1, - WindDirection.N: 0, - WindDirection.NE: 45, - WindDirection.E: 90, - WindDirection.SE: 135, - WindDirection.S: 180, - WindDirection.SW: 225, - WindDirection.W: 270, - WindDirection.NW: 315, - }[direction] - ) +class Wind(Model): + speed: int + gust: int + direction: WindDirection | None = None class WeatherValue(Model): date: datetime.datetime sky: Sky + wind: Wind temperature: list[int] - wind_speed: int - wind_gust: int - wind_direction: float precipitation: float pressure: list[int] = Field(default=[]) humidity: int = Field(default=-1) diff --git a/gallery/sketch/weather/parser.py b/gallery/sketch/weather/parser.py index e67c87c..6cf80b7 100644 --- a/gallery/sketch/weather/parser.py +++ b/gallery/sketch/weather/parser.py @@ -1,6 +1,6 @@ from typing import Self -from gallery.sketch.weather.model import WindDirection, WindDirectionDeg +from gallery.sketch.weather.model import WindDirection class ParseError(Exception): @@ -9,11 +9,7 @@ class ParseError(Exception): return cls("Invalid date string", date_str) -WIND_DIRECTION_MAP: dict[str, WindDirection] = { - "—": WindDirection.CALM, - "": WindDirection.CALM, - "штиль": WindDirection.CALM, - "штл": WindDirection.CALM, +WIND_DIRECTION_MAP: dict[str, WindDirection | None] = { "с": WindDirection.N, "св": WindDirection.NE, "в": WindDirection.E, @@ -25,5 +21,34 @@ WIND_DIRECTION_MAP: dict[str, WindDirection] = { } -def parse_wind_direction(value: str) -> WindDirectionDeg: - return WindDirectionDeg.from_direction(WIND_DIRECTION_MAP[value.replace("-", "").lower()]) +# pylint:disable=too-many-return-statements +def _parse_wind_direction_number(value: int | float) -> WindDirection | None: + if value > 337.5 or value <= 22.25: + return WindDirection.N + elif value <= 67.5: + return WindDirection.NE + elif value <= 112.5: + return WindDirection.E + elif value <= 157.5: + return WindDirection.SE + elif value <= 202.5: + return WindDirection.S + elif value <= 247.5: + return WindDirection.SW + elif value <= 292.5: + return WindDirection.W + elif value <= 337.5: + return WindDirection.NW + else: + return None + + +def _parse_wind_direction_str(value: str) -> WindDirection | None: + return WIND_DIRECTION_MAP.get(value.replace("-", "").lower()) + + +def parse_wind_direction(value: str | int | float) -> WindDirection | None: + if isinstance(value, str): + return _parse_wind_direction_str(value) + else: + return _parse_wind_direction_number(value) diff --git a/gallery/sketch/weather/util.py b/gallery/sketch/weather/util.py index f78e93f..ea1be53 100644 --- a/gallery/sketch/weather/util.py +++ b/gallery/sketch/weather/util.py @@ -1,8 +1,8 @@ import datetime import statistics -from typing import Literal +from typing import Counter, Literal -from .model import Cloudness, Precipitation, Sky, WeatherValue, WindDirectionDeg +from .model import Cloudness, Precipitation, Sky, WeatherValue, Wind MergeMode = Literal["interval", "average"] @@ -16,10 +16,12 @@ def build_weather_value(date: datetime.datetime) -> WeatherValue: thunder=False, fog=False, ), + wind=Wind( + speed=0, + gust=0, + direction=None, + ), temperature=[], - wind_speed=0, - wind_gust=0, - wind_direction=WindDirectionDeg(-1), precipitation=0, pressure=[], humidity=-1, @@ -55,18 +57,18 @@ def merge_weather_values( temperatures += value.temperature pressures += value.pressure humidities.append(value.humidity) - wind_speeds.append(value.wind_speed) - wind_gusts.append(value.wind_gust) - wind_directions.append(value.wind_direction) + wind_speeds.append(value.wind.speed) + wind_gusts.append(value.wind.gust) + wind_directions.append(value.wind.direction) cloudnesses.append(value.sky.cloudness) precipitations.append(value.sky.precipitation) precipitation += value.precipitation result.temperature = _merge_values(temperatures, mode) result.pressure = _merge_values(pressures, mode) result.humidity = round(statistics.mean(humidities)) - result.wind_speed = round(statistics.mean(wind_speeds)) - result.wind_gust = round(statistics.mean(wind_gusts)) - result.wind_direction = statistics.mean(wind_directions) + result.wind.speed = round(statistics.mean(wind_speeds)) + result.wind.gust = round(statistics.mean(wind_gusts)) + result.wind.direction = Counter(wind_directions).most_common(1)[0][0] for item in cloudnesses: if Cloudness.values.index(item) > Cloudness.values.index(result.sky.cloudness): result.sky.cloudness = item