File indexing completed on 2024-05-12 12:43:30
0001 /* 0002 * SPDX-FileCopyrightText: 2020-2021 Han Young <hanyoung@protonmail.com> 0003 * SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kweathercore_p.h" 0009 #include "metnoparser_p.h" 0010 0011 #include <KHolidays/SunRiseSet> 0012 #include <kholidays_version.h> 0013 0014 #include <QJsonArray> 0015 #include <QJsonDocument> 0016 #include <QJsonObject> 0017 0018 using namespace KWeatherCore; 0019 0020 void MetNoParser::parseLocationForecast(const QByteArray &data) 0021 { 0022 const QJsonDocument jsonDocument = QJsonDocument::fromJson(data); 0023 0024 if (jsonDocument.isObject()) { 0025 const QJsonObject obj = jsonDocument.object(); 0026 const QJsonObject prop = obj[QLatin1String("properties")].toObject(); 0027 0028 if (prop.contains(QLatin1String("timeseries"))) { 0029 const QJsonArray timeseries = prop[QLatin1String("timeseries")].toArray(); 0030 0031 // loop over all forecast data 0032 for (const auto &ref : std::as_const(timeseries)) { 0033 parseOneElement(ref.toObject()); 0034 } 0035 } 0036 } 0037 } 0038 0039 void MetNoParser::parseOneElement(const QJsonObject &obj) 0040 { 0041 const QJsonObject data = obj[QLatin1String("data")].toObject(); 0042 const QJsonObject instant = data[QLatin1String("instant")].toObject()[QLatin1String("details")].toObject(); 0043 // ignore last forecast, which does not have enough data 0044 if (!data.contains(QLatin1String("next_6_hours")) && !data.contains(QLatin1String("next_1_hours"))) { 0045 return; 0046 } 0047 0048 // get symbolCode and precipitation amount 0049 QString symbolCode; 0050 double precipitationAmount = 0; 0051 // some fields contain only "next_1_hours", and others may contain only 0052 // "next_6_hours" 0053 if (data.contains(QLatin1String("next_1_hours"))) { 0054 const QJsonObject nextOneHours = data[QLatin1String("next_1_hours")].toObject(); 0055 symbolCode = nextOneHours[QLatin1String("summary")].toObject()[QLatin1String("symbol_code")].toString(QLatin1String("unknown")); 0056 precipitationAmount = nextOneHours[QLatin1String("details")].toObject()[QLatin1String("precipitation_amount")].toDouble(); 0057 } else { 0058 const QJsonObject nextSixHours = data[QLatin1String("next_6_hours")].toObject(); 0059 symbolCode = nextSixHours[QLatin1String("summary")].toObject()[QLatin1String("symbol_code")].toString(QLatin1String("unknown")); 0060 precipitationAmount = nextSixHours[QLatin1String("details")].toObject()[QLatin1String("precipitation_amount")].toDouble(); 0061 } 0062 0063 symbolCode = symbolCode.split(QLatin1Char('_'))[0]; // trim _[day/night] from end - 0064 // https://api.met.no/weatherapi/weathericon/2.0/legends 0065 HourlyWeatherForecast hourForecast(QDateTime::fromString(obj.value(QLatin1String("time")).toString(), Qt::ISODate)); 0066 hourForecast.setNeutralWeatherIcon(KWeatherCorePrivate::resolveAPIWeatherDesc(symbolCode + QLatin1String("_neutral")).icon); 0067 hourForecast.setTemperature(instant[QLatin1String("air_temperature")].toDouble()); 0068 hourForecast.setPressure(instant[QLatin1String("air_pressure_at_sea_level")].toDouble()); 0069 hourForecast.setWindDirectionDegree(instant[QLatin1String("wind_from_direction")].toDouble()); 0070 hourForecast.setWindSpeed(instant[QLatin1String("wind_speed")].toDouble()); 0071 hourForecast.setHumidity(instant[QLatin1String("relative_humidity")].toDouble()); 0072 hourForecast.setFog(instant[QLatin1String("fog_area_fraction")].toDouble()); 0073 hourForecast.setUvIndex(instant[QLatin1String("ultraviolet_index_clear_sky")].toDouble()); 0074 hourForecast.setPrecipitationAmount(precipitationAmount); 0075 hourForecast.setSymbolCode(symbolCode); 0076 hourlyForecast.push_back(std::move(hourForecast)); 0077 } 0078 0079 bool MetNoParser::isDayTime(const QDateTime &dt) const 0080 { 0081 const auto sunriseTime = KHolidays::SunRiseSet::utcSunrise(dt.date(), forecast.latitude(), forecast.longitude()); 0082 const auto sunsetTime = KHolidays::SunRiseSet::utcSunset(dt.date(), forecast.latitude(), forecast.longitude()); 0083 0084 #if KHOLIDAYS_VERSION >= QT_VERSION_CHECK(5, 97, 0) 0085 // polar day/night: there is no sunrise/sunset 0086 if (!sunriseTime.isValid() || !sunsetTime.isValid()) { 0087 return KHolidays::SunRiseSet::isPolarDay(dt.date(), forecast.latitude()); 0088 } 0089 #endif 0090 0091 auto sunrise = QDateTime(dt.date(), sunriseTime, Qt::UTC); 0092 auto sunset = QDateTime(dt.date(), sunsetTime, Qt::UTC); 0093 0094 // sunset before sunrise means the sunset actually happens the next day 0095 if (dt >= sunrise && sunset < sunrise) { 0096 sunset = sunset.addDays(1); 0097 } else if (dt < sunrise && sunset < sunrise) { 0098 sunrise = sunrise.addDays(-1); 0099 } 0100 0101 // 30 min threshold 0102 return sunrise.addSecs(-1800) <= dt && sunset.addSecs(1800) >= dt; 0103 } 0104 0105 void MetNoParser::applySunriseToForecast(const QTimeZone &timezone) 0106 { 0107 // ************* Lambda *************** // 0108 auto getSymbolCodeDescription = [](bool isDay, const QString &symbolCode) { 0109 return isDay ? KWeatherCorePrivate::resolveAPIWeatherDesc(symbolCode + QStringLiteral("_day")).desc 0110 : KWeatherCorePrivate::resolveAPIWeatherDesc(symbolCode + QStringLiteral("_night")).desc; 0111 }; 0112 0113 auto getSymbolCodeIcon = [](bool isDay, const QString &symbolCode) { 0114 return isDay ? KWeatherCorePrivate::resolveAPIWeatherDesc(symbolCode + QStringLiteral("_day")).icon 0115 : KWeatherCorePrivate::resolveAPIWeatherDesc(symbolCode + QStringLiteral("_night")).icon; 0116 }; 0117 0118 // ******* code ******** // 0119 for (auto &hourForecast : hourlyForecast) { 0120 hourForecast.setDate(hourForecast.date().toTimeZone(timezone)); 0121 0122 bool isDay; 0123 isDay = isDayTime(hourForecast.date()); 0124 hourForecast.setWeatherIcon(getSymbolCodeIcon(isDay, hourForecast.symbolCode())); // set day/night icon 0125 hourForecast.setWeatherDescription(getSymbolCodeDescription(isDay, hourForecast.symbolCode())); 0126 forecast += std::move(hourForecast); 0127 } 0128 0129 // save to cache 0130 QFile file(KWeatherCorePrivate::getCacheDirectory(forecast.latitude(), forecast.longitude()).path() + QStringLiteral("/cache.json")); 0131 0132 if (file.open(QIODevice::WriteOnly)) { 0133 file.write(QJsonDocument(forecast.toJson()).toJson(QJsonDocument::Compact)); 0134 } else { 0135 qWarning() << "write to cache failed"; 0136 } 0137 }