open_meteo.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. """Open Meteo (weather)"""
  3. from urllib.parse import urlencode
  4. from datetime import datetime
  5. from searx.result_types import EngineResults, WeatherAnswer
  6. from searx import weather
  7. about = {
  8. "website": "https://open-meteo.com",
  9. "wikidata_id": None,
  10. "official_api_documentation": "https://open-meteo.com/en/docs",
  11. "use_official_api": True,
  12. "require_api_key": False,
  13. "results": "JSON",
  14. }
  15. categories = ["weather"]
  16. geo_url = "https://geocoding-api.open-meteo.com"
  17. api_url = "https://api.open-meteo.com"
  18. data_of_interest = (
  19. "temperature_2m",
  20. "apparent_temperature",
  21. "relative_humidity_2m",
  22. "apparent_temperature",
  23. "cloud_cover",
  24. "pressure_msl",
  25. "wind_speed_10m",
  26. "wind_direction_10m",
  27. "weather_code",
  28. # "visibility",
  29. # "is_day",
  30. )
  31. def request(query, params):
  32. try:
  33. location = weather.GeoLocation.by_query(query)
  34. except ValueError:
  35. return
  36. args = {
  37. "latitude": location.latitude,
  38. "longitude": location.longitude,
  39. "timeformat": "unixtime",
  40. "timezone": "auto", # use timezone of the location
  41. "format": "json",
  42. "current": ",".join(data_of_interest),
  43. "forecast_days": 3,
  44. "hourly": ",".join(data_of_interest),
  45. }
  46. params["url"] = f"{api_url}/v1/forecast?{urlencode(args)}"
  47. # https://open-meteo.com/en/docs#weather_variable_documentation
  48. # https://nrkno.github.io/yr-weather-symbols/
  49. WMO_TO_CONDITION: dict[int, weather.WeatherConditionType] = {
  50. # 0 Clear sky
  51. 0: "clear sky",
  52. # 1, 2, 3 Mainly clear, partly cloudy, and overcast
  53. 1: "fair",
  54. 2: "partly cloudy",
  55. 3: "cloudy",
  56. # 45, 48 Fog and depositing rime fog
  57. 45: "fog",
  58. 48: "fog",
  59. # 51, 53, 55 Drizzle: Light, moderate, and dense intensity
  60. 51: "light rain",
  61. 53: "light rain",
  62. 55: "light rain",
  63. # 56, 57 Freezing Drizzle: Light and dense intensity
  64. 56: "light sleet showers",
  65. 57: "light sleet",
  66. # 61, 63, 65 Rain: Slight, moderate and heavy intensity
  67. 61: "light rain",
  68. 63: "rain",
  69. 65: "heavy rain",
  70. # 66, 67 Freezing Rain: Light and heavy intensity
  71. 66: "light sleet showers",
  72. 67: "light sleet",
  73. # 71, 73, 75 Snow fall: Slight, moderate, and heavy intensity
  74. 71: "light sleet",
  75. 73: "sleet",
  76. 75: "heavy sleet",
  77. # 77 Snow grains
  78. 77: "snow",
  79. # 80, 81, 82 Rain showers: Slight, moderate, and violent
  80. 80: "light rain showers",
  81. 81: "rain showers",
  82. 82: "heavy rain showers",
  83. # 85, 86 Snow showers slight and heavy
  84. 85: "snow showers",
  85. 86: "heavy snow showers",
  86. # 95 Thunderstorm: Slight or moderate
  87. 95: "rain and thunder",
  88. # 96, 99 Thunderstorm with slight and heavy hail
  89. 96: "light snow and thunder",
  90. 99: "heavy snow and thunder",
  91. }
  92. def _weather_data(location: weather.GeoLocation, data: dict):
  93. return WeatherAnswer.Item(
  94. location=location,
  95. temperature=weather.Temperature(unit="°C", value=data["temperature_2m"]),
  96. condition=WMO_TO_CONDITION[data["weather_code"]],
  97. feels_like=weather.Temperature(unit="°C", value=data["apparent_temperature"]),
  98. wind_from=weather.Compass(data["wind_direction_10m"]),
  99. wind_speed=weather.WindSpeed(data["wind_speed_10m"], unit="km/h"),
  100. pressure=weather.Pressure(data["pressure_msl"], unit="hPa"),
  101. humidity=weather.RelativeHumidity(data["relative_humidity_2m"]),
  102. cloud_cover=data["cloud_cover"],
  103. )
  104. def response(resp):
  105. location = weather.GeoLocation.by_query(resp.search_params["query"])
  106. res = EngineResults()
  107. json_data = resp.json()
  108. weather_answer = WeatherAnswer(
  109. current=_weather_data(location, json_data["current"]),
  110. service="Open-meteo",
  111. # url="https://open-meteo.com/en/docs",
  112. )
  113. for index, time in enumerate(json_data["hourly"]["time"]):
  114. if time < json_data["current"]["time"]:
  115. # Cut off the hours that are already in the past
  116. continue
  117. hourly_data = {}
  118. for key in data_of_interest:
  119. hourly_data[key] = json_data["hourly"][key][index]
  120. forecast_data = _weather_data(location, hourly_data)
  121. forecast_data.datetime = weather.DateTime(datetime.fromtimestamp(time))
  122. weather_answer.forecasts.append(forecast_data)
  123. res.add(weather_answer)
  124. return res