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"