File indexing completed on 2025-02-16 04:48:28
0001 /* 0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "weatherforecast.h" 0008 #include "weathertile.h" 0009 0010 #include <KHolidays/SunRiseSet> 0011 0012 #include <QDateTime> 0013 #include <QDebug> 0014 0015 #include <limits> 0016 0017 class WeatherForecastPrivate : public QSharedData { 0018 public: 0019 bool useDayTimeIcon() const; 0020 0021 QDateTime m_dt; 0022 WeatherTile m_tile; 0023 float m_minTemp = std::numeric_limits<float>::max(); 0024 float m_maxTemp = std::numeric_limits<float>::lowest(); 0025 float m_precipitation = std::numeric_limits<float>::lowest(); 0026 float m_windSpeed = std::numeric_limits<float>::lowest(); 0027 WeatherForecast::SymbolType m_symbol = WeatherForecast::None; 0028 int m_range = 0; 0029 }; 0030 0031 WeatherForecast::WeatherForecast() 0032 : d(new WeatherForecastPrivate) 0033 { 0034 } 0035 0036 WeatherForecast::WeatherForecast(const WeatherForecast&) = default; 0037 WeatherForecast::~WeatherForecast() = default; 0038 WeatherForecast& WeatherForecast::operator=(const WeatherForecast&) = default; 0039 0040 bool WeatherForecast::isValid() const 0041 { 0042 return d->m_dt.isValid(); 0043 } 0044 0045 QDateTime WeatherForecast::dateTime() const 0046 { 0047 return d->m_dt; 0048 } 0049 0050 void WeatherForecast::setDateTime(const QDateTime &dt) 0051 { 0052 d.detach(); 0053 d->m_dt = dt; 0054 } 0055 0056 float WeatherForecast::minimumTemperature() const 0057 { 0058 return d->m_minTemp; 0059 } 0060 0061 void WeatherForecast::setMinimumTemperature(float t) 0062 { 0063 d.detach(); 0064 d->m_minTemp = t; 0065 } 0066 0067 float WeatherForecast::maximumTemperature() const 0068 { 0069 return d->m_maxTemp; 0070 } 0071 0072 void WeatherForecast::setMaximumTemperature(float t) 0073 { 0074 d.detach(); 0075 d->m_maxTemp = t; 0076 } 0077 0078 WeatherForecast::SymbolType WeatherForecast::symbolType() const 0079 { 0080 return d->m_symbol | (d->m_windSpeed > 10.8f ? WeatherForecast::Wind : WeatherForecast::None); 0081 } 0082 0083 void WeatherForecast::setSymbolType(WeatherForecast::SymbolType type) 0084 { 0085 d.detach(); 0086 d->m_symbol = type; 0087 } 0088 0089 // breeze icon mapping 0090 struct icon_map_t { 0091 WeatherForecast::SymbolType mask; 0092 const char *dayIcon; 0093 const char *nightIcon; 0094 }; 0095 0096 static const icon_map_t icon_map[] = { 0097 { WeatherForecast::Snow, "weather-snow", "weather-snow" }, 0098 { WeatherForecast::LightSnow | WeatherForecast::Clear, "weather-snow-scattered-day", "weather-snow-scattered-night" }, 0099 { WeatherForecast::LightSnow, "weather-snow-scattered", "weather-snow-scattered" }, 0100 0101 { WeatherForecast::Hail, "weather-hail", "weather-hail" }, 0102 0103 { WeatherForecast::Clear | WeatherForecast::ThunderStorm, "weather-storm-day", "weather-storm-night" }, 0104 { WeatherForecast::ThunderStorm, "weather-storm", "weather-storm" }, 0105 0106 { WeatherForecast::Clear | WeatherForecast::Rain, "weather-showers-day", "weather-showers-night" }, 0107 { WeatherForecast::Rain, "weather-showers", "weather-showers" }, 0108 { WeatherForecast::Clear | WeatherForecast::LightRain, "weather-showers-scattered-day", "weather-showers-scattered-night" }, 0109 { WeatherForecast::LightRain, "weather-showers-scattered", "weather-showers-scattered" }, 0110 0111 { WeatherForecast::Clear | WeatherForecast::Clouds | WeatherForecast::Wind, "weather-clouds-wind", "weather-clouds-wind-night" }, 0112 { WeatherForecast::Clear | WeatherForecast::Clouds, "weather-clouds", "weather-clouds-night" }, 0113 { WeatherForecast::Clouds | WeatherForecast::Wind, "weather-many-clouds-wind", "weather-many-clouds-wind" }, 0114 { WeatherForecast::Clouds, "weather-many-clouds", "weather-many-clouds" }, 0115 { WeatherForecast::Fog, "weather-fog", "weather-fog" }, 0116 { WeatherForecast::LightClouds | WeatherForecast::Wind, "weather-few-clouds-wind", "weather-few-clouds-wind-night" }, 0117 { WeatherForecast::LightClouds, "weather-few-clouds", "weather-few-clouds-night" }, 0118 { WeatherForecast::Clear | WeatherForecast::Wind, "weather-clear-wind", "weather-clear-wind-night" }, 0119 { WeatherForecast::Clear, "weather-clear", "weather-clear-night" } 0120 }; 0121 0122 bool WeatherForecastPrivate::useDayTimeIcon() const 0123 { 0124 if (m_range >= 24) { 0125 return true; 0126 } 0127 0128 const auto sunriseTime = KHolidays::SunRiseSet::utcSunrise(m_dt.date(), m_tile.latitude(), m_tile.longitude()); 0129 const auto sunsetTime = KHolidays::SunRiseSet::utcSunset(m_dt.date(), m_tile.latitude(), m_tile.longitude()); 0130 0131 // polar day/night: there is no sunrise/sunset 0132 if (!sunriseTime.isValid() || !sunsetTime.isValid()) { 0133 return KHolidays::SunRiseSet::isPolarDay(m_dt.date(), m_tile.latitude()); 0134 } 0135 0136 auto sunrise = QDateTime(m_dt.date(), sunriseTime, Qt::UTC); 0137 auto sunset = QDateTime(m_dt.date(), sunsetTime, Qt::UTC); 0138 0139 // sunset before sunrise means the sunset actually happens the next day 0140 if (m_dt >= sunrise && sunset < sunrise) { 0141 sunset = sunset.addDays(1); 0142 } else if (m_dt < sunrise && sunset < sunrise) { 0143 sunrise = sunrise.addDays(-1); 0144 } 0145 0146 // check overlap for two days, otherwise we might miss one on a day boundary 0147 const auto endDt = m_dt.addSecs(m_range * 3600); 0148 return sunrise < endDt && sunset > m_dt; 0149 } 0150 0151 QString WeatherForecast::symbolIconName() const 0152 { 0153 for (const auto &icon : icon_map) { 0154 if ((icon.mask & symbolType()) == icon.mask) { 0155 return QLatin1StringView(d->useDayTimeIcon() ? icon.dayIcon : icon.nightIcon); 0156 } 0157 } 0158 return {}; 0159 } 0160 0161 float WeatherForecast::precipitation() const 0162 { 0163 return std::max(d->m_precipitation, 0.0f); 0164 } 0165 0166 void WeatherForecast::setPrecipitation(float precipitation) 0167 { 0168 d.detach(); 0169 d->m_precipitation = precipitation; 0170 } 0171 0172 float WeatherForecast::windSpeed() const 0173 { 0174 return std::max(d->m_windSpeed, 0.0f); 0175 } 0176 0177 void WeatherForecast::setWindSpeed(float speed) 0178 { 0179 d.detach(); 0180 d->m_windSpeed = speed; 0181 } 0182 0183 void WeatherForecast::merge(const WeatherForecast &other) 0184 { 0185 d.detach(); 0186 if (d->m_minTemp == std::numeric_limits<float>::max() || other.range() <= d->m_range) { 0187 d->m_minTemp = std::min(other.minimumTemperature(), d->m_minTemp); 0188 } 0189 if (d->m_maxTemp == std::numeric_limits<float>::min() || other.range() <= d->m_range) { 0190 d->m_maxTemp = std::max(other.maximumTemperature(), d->m_maxTemp); 0191 } 0192 if (d->m_precipitation < 0.0f || other.range() <= d->m_range) { 0193 d->m_precipitation = std::max(other.precipitation(), d->m_precipitation); 0194 } 0195 if (d->m_windSpeed < 0.0f || other.range() <= d->m_range) { 0196 d->m_windSpeed = std::max(other.windSpeed(), d->m_windSpeed); 0197 } 0198 0199 if (d->m_symbol == None || other.range() <= d->m_range) { 0200 d->m_symbol |= other.symbolType(); 0201 } 0202 } 0203 0204 int WeatherForecast::range() const 0205 { 0206 return d->m_range; 0207 } 0208 0209 void WeatherForecast::setRange(int hours) 0210 { 0211 d.detach(); 0212 d->m_range = hours; 0213 } 0214 0215 WeatherTile WeatherForecast::tile() const 0216 { 0217 return d->m_tile; 0218 } 0219 0220 void WeatherForecast::setTile(WeatherTile tile) 0221 { 0222 d.detach(); 0223 d->m_tile = tile; 0224 } 0225 0226 bool WeatherForecast::isSevere() const 0227 { 0228 // heat: https://en.wikipedia.org/wiki/Heat_index 0229 // TODO to do this properly we need the humidity value as well 0230 if (d->m_maxTemp > 35.0) { 0231 return true; 0232 } 0233 0234 // cold 0235 if (d->m_minTemp < -20.0) { 0236 return true; 0237 } 0238 0239 // precipitation: https://de.wikipedia.org/wiki/Unwetter 0240 if ((d->m_precipitation / d->m_range) > 25.0 || (d->m_precipitation > 35.0 && d->m_range >= 6)) { 0241 return true; 0242 } 0243 0244 // wind: https://en.wikipedia.org/wiki/Beaufort_scale 0245 if (d->m_windSpeed > 17.5) { 0246 return true; 0247 } 0248 0249 0250 return false; 0251 } 0252 0253 #include "moc_weatherforecast.cpp"