File indexing completed on 2024-04-28 04:42:42

0001 /*
0002  * SPDX-FileCopyrightText: 2020-2021 Han Young <hanyoung@protonmail.com>
0003  * SPDX-FileCopyrightText: 2020-2021 Devin Lin <espidev@gmail.com>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "kweathercore_p.h"
0009 
0010 #include <KLazyLocalizedString>
0011 
0012 #include <QStandardPaths>
0013 
0014 #include <cstring>
0015 #include <iomanip>
0016 #include <sstream>
0017 
0018 namespace KWeatherCore
0019 {
0020 CAPPolygon KWeatherCorePrivate::stringToPolygon(const QString &str)
0021 {
0022     CAPPolygon res;
0023 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
0024     const auto pairList = QStringView(str).split(QLatin1Char(' '), Qt::SkipEmptyParts);
0025 #else
0026     const auto pairList = str.splitRef(QLatin1Char(' '), Qt::SkipEmptyParts);
0027 #endif
0028 
0029     res.reserve(pairList.size());
0030     for (auto &pair : pairList) {
0031         auto coordinate = pair.split(QLatin1Char(','));
0032         if (coordinate.size() != 2) {
0033             continue;
0034         }
0035         bool latOk = false, lonOk = false;
0036         res.push_back({coordinate.front().toFloat(&latOk), coordinate.back().toFloat(&lonOk)});
0037         if (!latOk || !lonOk) {
0038             res.pop_back();
0039         }
0040     }
0041     return res;
0042 }
0043 
0044 QString KWeatherCorePrivate::toFixedString(double num)
0045 {
0046     std::ostringstream oss;
0047     oss << std::fixed << std::setprecision(2) << num;
0048     return QString::fromStdString(oss.str());
0049 }
0050 
0051 QDir KWeatherCorePrivate::getCacheDirectory(double latitude, double longitude)
0052 {
0053 #ifdef Q_OS_ANDROID
0054     QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/cache/") + toFixedString(latitude) + QStringLiteral("/")
0055              + toFixedString(longitude));
0056     if (!dir.exists())
0057         dir.mkpath(QStringLiteral("."));
0058     return dir;
0059 #else
0060     QDir dir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/kweather/cache/") + toFixedString(latitude)
0061              + QStringLiteral("/") + toFixedString(longitude));
0062     if (!dir.exists()) {
0063         dir.mkpath(QStringLiteral("."));
0064     }
0065     return dir;
0066 #endif
0067 }
0068 
0069 CAPAlertInfo::Urgency KWeatherCorePrivate::urgencyStringToEnum(const QString &str)
0070 {
0071     if (str == QLatin1String("Immediate")) {
0072         return CAPAlertInfo::Urgency::Immediate;
0073     } else if (str == QLatin1String("Expected")) {
0074         return CAPAlertInfo::Urgency::Expected;
0075     } else if (str == QLatin1String("Future")) {
0076         return CAPAlertInfo::Urgency::Future;
0077     } else if (str == QLatin1String("Past")) {
0078         return CAPAlertInfo::Urgency::Past;
0079     } else {
0080         return CAPAlertInfo::Urgency::UnknownUrgency;
0081     }
0082 }
0083 
0084 CAPAlertInfo::Severity KWeatherCorePrivate::severityStringToEnum(const QString &str)
0085 {
0086     if (str == QLatin1String("Extreme")) {
0087         return CAPAlertInfo::Severity::Extreme;
0088     } else if (str == QLatin1String("Severe")) {
0089         return CAPAlertInfo::Severity::Severe;
0090     } else if (str == QLatin1String("Moderate")) {
0091         return CAPAlertInfo::Severity::Moderate;
0092     } else if (str == QLatin1String("Minor")) {
0093         return CAPAlertInfo::Severity::Minor;
0094     } else {
0095         return CAPAlertInfo::Severity::UnknownSeverity;
0096     }
0097 }
0098 
0099 CAPAlertInfo::Certainty KWeatherCorePrivate::certaintyStringToEnum(const QString &str)
0100 {
0101     if (str == QLatin1String("Observed")) {
0102         return CAPAlertInfo::Certainty::Observed;
0103     } else if (str == QLatin1String("Likely")) {
0104         return CAPAlertInfo::Certainty::Likely;
0105     } else if (str == QLatin1String("Possible")) {
0106         return CAPAlertInfo::Certainty::Possible;
0107     } else if (str == QLatin1String("Unlikely")) {
0108         return CAPAlertInfo::Certainty::Unlikely;
0109     } else {
0110         return CAPAlertInfo::Certainty::UnknownCertainty;
0111     }
0112 }
0113 
0114 QString KWeatherCorePrivate::urgencyToString(CAPAlertInfo::Urgency urgency)
0115 {
0116     QString res;
0117     switch (urgency) {
0118     case CAPAlertInfo::Urgency::Immediate:
0119         res = i18n("Immediate");
0120         break;
0121     case CAPAlertInfo::Urgency::Expected:
0122         res = i18n("Expected");
0123         break;
0124     case CAPAlertInfo::Urgency::Future:
0125         res = i18n("Future");
0126         break;
0127     case CAPAlertInfo::Urgency::Past:
0128         res = i18n("Past");
0129         break;
0130     case CAPAlertInfo::Urgency::UnknownUrgency:
0131         res = i18n("Unknown");
0132     }
0133     return res;
0134 }
0135 
0136 QString KWeatherCorePrivate::severityToString(CAPAlertInfo::Severity severity)
0137 {
0138     QString res;
0139     switch (severity) {
0140     case CAPAlertInfo::Severity::Extreme:
0141         res = i18n("Extreme");
0142         break;
0143     case CAPAlertInfo::Severity::Severe:
0144         res = i18n("Severe");
0145         break;
0146     case CAPAlertInfo::Severity::Moderate:
0147         res = i18n("Moderate");
0148         break;
0149     case CAPAlertInfo::Severity::Minor:
0150         res = i18n("Minor");
0151         break;
0152     case CAPAlertInfo::Severity::UnknownSeverity:
0153         res = i18n("Unknown");
0154     }
0155     return res;
0156 }
0157 
0158 QString KWeatherCorePrivate::certaintyToString(CAPAlertInfo::Certainty certainty)
0159 {
0160     QString res;
0161     switch (certainty) {
0162     case CAPAlertInfo::Certainty::Observed:
0163         res = i18n("Observed");
0164         break;
0165     case CAPAlertInfo::Certainty::Likely:
0166         res = i18n("Likely");
0167         break;
0168     case CAPAlertInfo::Certainty::Possible:
0169         res = i18n("Possible");
0170         break;
0171     case CAPAlertInfo::Certainty::Unlikely:
0172         res = i18n("Unlikely");
0173         break;
0174     case CAPAlertInfo::Certainty::UnknownCertainty:
0175         res = i18n("Unknown");
0176     }
0177     return res;
0178 }
0179 
0180 // rank weather (for what best describes the day overall)
0181 // only need neutral icons
0182 // ### needs to be alphabetically sorted by icon name!
0183 struct {
0184     const char *iconName;
0185     int rank;
0186 } static constexpr const WEATHER_ICON_PRIORITY_RANK[] = {
0187     {"weather-clear", 0},
0188     {"weather-clouds", 2},
0189     {"weather-few-clouds", 1},
0190     {"weather-fog", 3},
0191     {"weather-freezing-rain", 6},
0192     {"weather-freezing-scattered-rain", 6},
0193     {"weather-freezing-scattered-rain-storm", 7},
0194     {"weather-freezing-storm", 6},
0195     {"weather-hail", 5},
0196     {"weather-mist", 3},
0197     {"weather-none-available", -1},
0198     {"weather-showers", 5},
0199     {"weather-showers-scattered", 4},
0200     {"weather-showers-scattered-storm", 7},
0201     {"weather-snow", 5},
0202     {"weather-snow-rain", 6},
0203     {"weather-snow-scattered", 4},
0204     {"weather-snow-scattered-storm", 7},
0205     {"weather-snow-storm", 7},
0206     {"weather-storm", 7},
0207 };
0208 
0209 int KWeatherCorePrivate::weatherIconPriorityRank(const QString &icon)
0210 {
0211     const auto it = std::lower_bound(std::begin(WEATHER_ICON_PRIORITY_RANK), std::end(WEATHER_ICON_PRIORITY_RANK), icon, [](const auto &lhs, const auto &rhs) {
0212         return QLatin1String(lhs.iconName) < rhs;
0213     });
0214     if (it != std::end(WEATHER_ICON_PRIORITY_RANK) && QLatin1String((*it).iconName) == icon) {
0215         return (*it).rank;
0216     }
0217 
0218     return 0;
0219 }
0220 
0221 // https://api.met.no/weatherapi/weathericon/2.0/legends
0222 // ### needs to be alphabetically sorted by key!
0223 struct {
0224     const char *key;
0225     const char *icon;
0226     const KLazyLocalizedString desc;
0227 } static constexpr const WEATHER_API_DESC_MAP[] = {
0228     {"clearsky_day", "weather-clear", kli18n("Clear")},
0229     {"clearsky_neutral", "weather-clear", kli18n("Clear")},
0230     {"clearsky_night", "weather-clear-night", kli18n("Clear")},
0231     {"cloudy_day", "weather-clouds", kli18n("Cloudy")},
0232     {"cloudy_neutral", "weather-clouds", kli18n("Cloudy")},
0233     {"cloudy_night", "weather-clouds-night", kli18n("Cloudy")},
0234     {"fair_day", "weather-few-clouds", kli18n("Partly Sunny")},
0235     {"fair_neutral", "weather-few-clouds", kli18n("Light Clouds")},
0236     {"fair_night", "weather-few-clouds-night", kli18n("Light Clouds")},
0237     {"fog_day", "weather-fog", kli18n("Fog")},
0238     {"fog_neutral", "weather-fog", kli18n("Fog")},
0239     {"fog_night", "weather-fog", kli18n("Fog")},
0240 
0241     {"heavyrain_day", "weather-showers-day", kli18n("Heavy Rain")},
0242     {"heavyrain_neutral", "weather-showers", kli18n("Heavy Rain")},
0243     {"heavyrain_night", "weather-showers-night", kli18n("Heavy Rain")},
0244     {"heavyrainandthunder_day", "weather-storm-day", kli18n("Thunderstorm with Heavy Rain")},
0245     {"heavyrainandthunder_neutral", "weather-storm", kli18n("Thunderstorm with Heavy Rain")},
0246     {"heavyrainandthunder_night", "weather-storm-night", kli18n("Thunderstorm with Heavy Rain")},
0247     {"heavyrainshowers_day", "weather-showers-day", kli18n("Heavy Rain Shower")},
0248     {"heavyrainshowers_neutral", "weather-showers", kli18n("Heavy Rain Shower")},
0249     {"heavyrainshowers_night", "weather-showers-night", kli18n("Heavy Rain Shower")},
0250     {"heavyrainshowersandthunder_day", "weather-storm-day", kli18n("Thunderstorm with Heavy Rain Shower")},
0251     {"heavyrainshowersandthunder_neutral", "weather-storm", kli18n("Thunderstorm with Heavy Rain Shower")},
0252     {"heavyrainshowersandthunder_night", "weather-storm-night", kli18n("Thunderstorm with Heavy Rain Shower")},
0253 
0254     {"heavysleet_day", "weather-freezing-rain", kli18n("Heavy Sleet")},
0255     {"heavysleet_neutral", "weather-freezing-rain", kli18n("Heavy Sleet")},
0256     {"heavysleet_night", "weather-freezing-rain", kli18n("Heavy Sleet")},
0257     {"heavysleetandthunder_day", "weather-freezing-storm", kli18n("Thunderstorm with Heavy Sleet")},
0258     {"heavysleetandthunder_neutral", "weather-freezing-storm", kli18n("Thunderstorm with Heavy Sleet")},
0259     {"heavysleetandthunder_night", "weather-freezing-storm", kli18n("Thunderstorm with Heavy Sleet")},
0260     {"heavysleetshowers_day", "weather-freezing-rain-day", kli18n("Heavy Sleet Shower")},
0261     {"heavysleetshowers_neutral", "weather-freezing-rain", kli18n("Heavy Sleet Shower")},
0262     {"heavysleetshowers_night", "weather-freezing-rain-night", kli18n("Heavy Sleet Shower")},
0263     {"heavysleetshowersandthunder_day", "weather-freezing-storm-day", kli18n("Thunderstorm with Heavy Sleet Shower")},
0264     {"heavysleetshowersandthunder_neutral", "weather-freezing-storm", kli18n("Thunderstorm with Heavy Sleet Shower")},
0265     {"heavysleetshowersandthunder_night", "weather-freezing-storm-night", kli18n("Thunderstorm with Heavy Sleet Shower")},
0266 
0267     {"heavysnow_day", "weather-snow", kli18n("Heavy Snow")},
0268     {"heavysnow_neutral", "weather-snow", kli18n("Heavy Snow")},
0269     {"heavysnow_night", "weather-snow", kli18n("Heavy Snow")},
0270     {"heavysnowshowers_day", "weather-snow-day", kli18n("Heavy Snow Shower")},
0271     {"heavysnowshowers_neutral", "weather-snow", kli18n("Heavy Snow Shower")},
0272     {"heavysnowshowers_night", "weather-snow-night", kli18n("Heavy Snow Shower")},
0273     {"heavysnowshowersandthunder_day", "weather-snow-storm-day", kli18n("Thunderstorm with Heavy Snow Shower")},
0274     {"heavysnowshowersandthunder_neutral", "weather-snow-storm", kli18n("Thunderstorm with Heavy Snow Shower")},
0275     {"heavysnowshowersandthunder_night", "weather-snow-storm-night", kli18n("Thunderstorm with Heavy Snow Shower")},
0276 
0277     {"lightrain_day", "weather-showers-scattered", kli18n("Light Rain")},
0278     {"lightrain_neutral", "weather-showers-scattered", kli18n("Light Rain")},
0279     {"lightrain_night", "weather-showers-scattered", kli18n("Light Rain")},
0280     {"lightrainandthunder_day", "weather-showers-scattered-storm", kli18n("Thunderstorm with Light Rain")},
0281     {"lightrainandthunder_neutral", "weather-showers-scattered-storm", kli18n("Thunderstorm with Light Rain")},
0282     {"lightrainandthunder_night", "weather-showers-scattered-storm", kli18n("Thunderstorm with Light Rain")},
0283     {"lightrainshowers_day", "weather-showers-scattered-day", kli18n("Light Rain Shower")},
0284     {"lightrainshowers_neutral", "weather-showers-scattered", kli18n("Light Rain Shower")},
0285     {"lightrainshowers_night", "weather-showers-scattered-night", kli18n("Light Rain Shower")},
0286     {"lightrainshowersandthunder_day", "weather-showers-scattered-storm-day", kli18n("Thunderstorm with Light Rain Shower")},
0287     {"lightrainshowersandthunder_neutral", "weather-showers-scattered-storm", kli18n("Thunderstorm with Light Rain Shower")},
0288     {"lightrainshowersandthunder_night", "weather-showers-scattered-storm-night", kli18n("Thunderstorm with Light Rain Shower")},
0289 
0290     {"lightsleet_day", "weather-showers-scattered", kli18n("Light Sleet")},
0291     {"lightsleet_neutral", "weather-showers-scattered", kli18n("Light Sleet")},
0292     {"lightsleet_night", "weather-showers-scattered", kli18n("Light Sleet")},
0293     {"lightsleetandthunder_day", "weather-freezing-scattered-rain-storm", kli18n("Thunderstorm with Light Sleet")},
0294     {"lightsleetandthunder_neutral", "weather-freezing-scattered-rain-storm", kli18n("Thunderstorm with Light Sleet")},
0295     {"lightsleetandthunder_night", "weather-freezing-scattered-rain-storm", kli18n("Thunderstorm with Light Sleet")},
0296     {"lightsleetshowers_day", "weather-freezing-scattered-rain-day", kli18n("Light Sleet Shower")},
0297     {"lightsleetshowers_neutral", "weather-freezing-scattered-rain", kli18n("Light Sleet Shower")},
0298     {"lightsleetshowers_night", "weather-freezing-scattered-rain-night", kli18n("Light Sleet Shower")},
0299 
0300     {"lightsnow_day", "weather-snow-scattered", kli18n("Light Snow")},
0301     {"lightsnow_neutral", "weather-snow-scattered", kli18n("Light Snow")},
0302     {"lightsnow_night", "weather-snow-scattered", kli18n("Light Snow")},
0303     {"lightsnowandthunder_day", "weather-snow-scattered-storm", kli18n("Thunderstorm with Light Snow")},
0304     {"lightsnowandthunder_neutral", "weather-snow-scattered-storm", kli18n("Thunderstorm with Light Snow")},
0305     {"lightsnowandthunder_night", "weather-snow-scattered-storm", kli18n("Thunderstorm with Light Snow")},
0306     {"lightsnowshowers_day", "weather-snow-scattered-day", kli18n("Light Snow Shower")},
0307     {"lightsnowshowers_neutral", "weather-snow-scattered", kli18n("Light Snow Shower")},
0308     {"lightsnowshowers_night", "weather-snow-scattered-night", kli18n("Light Snow Shower")},
0309 
0310     {"lightssleetshowersandthunder_day", "weather-freezing-scattered-rain-storm-day", kli18n("Thunderstorm with Light Sleet Shower")},
0311     {"lightssleetshowersandthunder_neutral", "weather-freezing-scattered-rain-storm", kli18n("Thunderstorm with Light Sleet Shower")},
0312     {"lightssleetshowersandthunder_night", "weather-freezing-scattered-rain-storm-night", kli18n("Thunderstorm with Light Sleet Shower")},
0313     {"lightssnowshowersandthunder_day", "weather-snow-scattered-storm-day", kli18n("Thunderstorm with Light Snow Shower")},
0314     {"lightssnowshowersandthunder_neutral", "weather-snow-scattered-storm", kli18n("Thunderstorm with Light Snow Shower")},
0315     {"lightssnowshowersandthunder_night", "weather-snow-scattered-storm-night", kli18n("Thunderstorm with Light Snow Shower")},
0316 
0317     {"partlycloudy_day", "weather-clouds", kli18n("Partly Cloudy")},
0318     {"partlycloudy_neutral", "weather-clouds", kli18n("Partly Cloudy")},
0319     {"partlycloudy_night", "weather-clouds-night", kli18n("Partly Cloudy")},
0320 
0321     {"rain_day", "weather-showers", kli18n("Rain")},
0322     {"rain_neutral", "weather-showers", kli18n("Rain")},
0323     {"rain_night", "weather-showers", kli18n("Rain")},
0324     {"rainandthunder_day", "weather-storm", kli18n("Thunderstorm with Rain")},
0325     {"rainandthunder_neutral", "weather-storm", kli18n("Thunderstorm with Rain")},
0326     {"rainandthunder_night", "weather-storm", kli18n("Thunderstorm with Rain")},
0327     {"rainshowers_day", "weather-showers-day", kli18n("Rain Shower")},
0328     {"rainshowers_neutral", "weather-showers", kli18n("Rain Shower")},
0329     {"rainshowers_night", "weather-showers-night", kli18n("Rain Shower")},
0330     {"rainshowersandthunder_day", "weather-storm-day", kli18n("Thunderstorm with Rain Shower")},
0331     {"rainshowersandthunder_neutral", "weather-storm", kli18n("Thunderstorm with Rain Shower")},
0332     {"rainshowersandthunder_night", "weather-storm-night", kli18n("Thunderstorm with Rain Shower")},
0333 
0334     {"sleet_day", "weather-freezing-rain", kli18n("Sleet")},
0335     {"sleet_neutral", "weather-freezing-rain", kli18n("Sleet")},
0336     {"sleet_night", "weather-freezing-rain", kli18n("Sleet")},
0337     {"sleetandthunder_day", "weather-storm", kli18n("Thunderstorm with Sleet")},
0338     {"sleetandthunder_neutral", "weather-storm", kli18n("Thunderstorm with Sleet")},
0339     {"sleetandthunder_night", "weather-storm", kli18n("Thunderstorm with Sleet")},
0340     {"sleetshowers_day", "weather-freezing-rain-day", kli18n("Sleet Shower")},
0341     {"sleetshowers_neutral", "weather-freezing-rain", kli18n("Sleet Shower")},
0342     {"sleetshowers_night", "weather-freezing-rain-night", kli18n("Sleet Shower")},
0343     {"sleetshowersandthunder_day", "weather-freezing-storm-day", kli18n("Thunderstorm with Sleet Shower")},
0344     {"sleetshowersandthunder_neutral", "weather-freezing-storm", kli18n("Thunderstorm with Sleet Shower")},
0345     {"sleetshowersandthunder_night", "weather-freezing-storm-night", kli18n("Thunderstorm with Sleet Shower")},
0346 
0347     {"snow_day", "weather-snow", kli18n("Snow")},
0348     {"snow_neutral", "weather-snow", kli18n("Snow")},
0349     {"snow_night", "weather-snow", kli18n("Snow")},
0350     {"snowandthunder_day", "weather-snow-storm", kli18n("Thunderstorm with Snow")},
0351     {"snowandthunder_neutral", "weather-snow-storm", kli18n("Thunderstorm with Snow")},
0352     {"snowandthunder_night", "weather-snow-storm", kli18n("Thunderstorm with Snow")},
0353     {"snowshowers_day", "weather-snow-day", kli18n("Snow Shower")},
0354     {"snowshowers_neutral", "weather-snow", kli18n("Snow Shower")},
0355     {"snowshowers_night", "weather-snow-night", kli18n("Snow Shower")},
0356     {"snowshowersandthunder_day", "weather-snow-storm-day", kli18n("Thunderstorm with Snow Shower")},
0357     {"snowshowersandthunder_neutral", "weather-snow-storm", kli18n("Thunderstorm with Snow Shower")},
0358     {"snowshowersandthunder_night", "weather-snow-storm-night", kli18n("Thunderstorm with Snow Shower")},
0359 
0360 };
0361 
0362 ResolvedWeatherDesc KWeatherCorePrivate::resolveAPIWeatherDesc(const QString &desc)
0363 {
0364     const auto key = desc.toUtf8();
0365     const auto it = std::lower_bound(std::begin(WEATHER_API_DESC_MAP), std::end(WEATHER_API_DESC_MAP), key, [](const auto &lhs, const auto &rhs) {
0366         return std::strcmp(lhs.key, rhs.constData()) < 0;
0367     });
0368     if (it != std::end(WEATHER_API_DESC_MAP) && std::strcmp((*it).key, key.constData()) == 0) {
0369         return ResolvedWeatherDesc(QLatin1String((*it).icon), (*it).desc.toString());
0370     }
0371 
0372     return {};
0373 }
0374 
0375 }