#include "WeatherSummaryFormatter.h" #include #include namespace { QString locationDisplayName(const WeatherLocation &location) { QStringList parts; if (!location.cityName.trimmed().isEmpty()) { parts.append(location.cityName.trimmed()); } if (!location.adminName.trimmed().isEmpty() && location.adminName.trimmed() != location.cityName.trimmed()) { parts.append(location.adminName.trimmed()); } if (!location.countryName.trimmed().isEmpty() && location.countryName.trimmed() != location.adminName.trimmed()) { parts.append(location.countryName.trimmed()); } return parts.isEmpty() ? QStringLiteral("当前位置") : parts.join(QStringLiteral(",")); } QString candidateDisplayName(const WeatherLocationCandidate &candidate) { QStringList parts; if (!candidate.cityName.trimmed().isEmpty()) { parts.append(candidate.cityName.trimmed()); } if (!candidate.adminName.trimmed().isEmpty() && candidate.adminName.trimmed() != candidate.cityName.trimmed()) { parts.append(candidate.adminName.trimmed()); } if (!candidate.countryName.trimmed().isEmpty() && candidate.countryName.trimmed() != candidate.adminName.trimmed()) { parts.append(candidate.countryName.trimmed()); } return parts.isEmpty() ? QStringLiteral("未知地点") : parts.join(QStringLiteral(",")); } QString sourcePrefix(const WeatherLocation &location) { switch (location.source) { case WeatherLocationSource::SettingsDefault: return QStringLiteral("使用设置页默认城市:%1。\n").arg(locationDisplayName(location)); case WeatherLocationSource::IpFallback: return QStringLiteral("根据公网 IP 判断城市为:%1。\n").arg(locationDisplayName(location)); case WeatherLocationSource::ExplicitCity: return {}; } return {}; } QString ambiguityPrefix(const WeatherReport &report) { if (!report.hasLocationAmbiguity || report.locationCandidates.size() <= 1) { return {}; } QStringList otherCandidates; const int maxOtherCandidateCount = qMin(3, report.locationCandidates.size() - 1); for (int index = 1; index <= maxOtherCandidateCount; ++index) { otherCandidates.append(candidateDisplayName(report.locationCandidates.at(index))); } if (otherCandidates.isEmpty()) { return QStringLiteral("可能存在同名城市,当前使用:%1。\n").arg(locationDisplayName(report.location)); } return QStringLiteral("可能存在同名城市,当前使用:%1。其他候选包括:%2。\n") .arg(locationDisplayName(report.location), otherCandidates.join(QStringLiteral(";"))); } QString temperatureText(double value) { return QStringLiteral("%1℃").arg(QString::number(value, 'f', 1)); } QString currentText(const WeatherReport &report) { const WeatherCurrent ¤t = report.current; QStringList details; if (current.hasTemperature) { details.append(QStringLiteral("温度 %1").arg(temperatureText(current.temperatureC))); } if (current.hasApparentTemperature) { details.append(QStringLiteral("体感 %1").arg(temperatureText(current.apparentTemperatureC))); } if (current.hasHumidity) { details.append(QStringLiteral("湿度 %1%").arg(QString::number(current.humidityPercent, 'f', 0))); } if (current.hasWindSpeed) { const QString direction = current.hasWindDirection ? WeatherSummaryFormatter::windDirectionText(current.windDirectionDegree) : QStringLiteral("风"); details.append(QStringLiteral("%1 %2 km/h").arg(direction, QString::number(current.windSpeedKmh, 'f', 1))); } if (current.hasPrecipitation) { details.append(QStringLiteral("降水 %1 mm").arg(QString::number(current.precipitationMm, 'f', 1))); } QString text = QStringLiteral("%1当前天气:%2") .arg(locationDisplayName(report.location), WeatherSummaryFormatter::weatherCodeText(current.weatherCode)); if (!details.isEmpty()) { text += QStringLiteral(",") + details.join(QStringLiteral(",")); } if (current.time.isValid()) { text += QStringLiteral("。更新时间:%1").arg(current.time.toString(QStringLiteral("HH:mm"))); } text += QStringLiteral("。"); return text; } QString dailyText(const WeatherDailyForecast &daily, const QString &prefix) { QStringList details; if (daily.hasTemperatureMax && daily.hasTemperatureMin) { details.append(QStringLiteral("%1-%2").arg(temperatureText(daily.temperatureMinC), temperatureText(daily.temperatureMaxC))); } else if (daily.hasTemperatureMax) { details.append(QStringLiteral("最高 %1").arg(temperatureText(daily.temperatureMaxC))); } else if (daily.hasTemperatureMin) { details.append(QStringLiteral("最低 %1").arg(temperatureText(daily.temperatureMinC))); } if (daily.hasPrecipitationProbability) { details.append(QStringLiteral("降水概率 %1%").arg(QString::number(daily.precipitationProbabilityPercent, 'f', 0))); } QString text = QStringLiteral("%1:%2").arg(prefix, WeatherSummaryFormatter::weatherCodeText(daily.weatherCode)); if (!details.isEmpty()) { text += QStringLiteral(",") + details.join(QStringLiteral(",")); } return text; } const WeatherDailyForecast *forecastForOffset(const QVector &forecasts, int offset) { if (offset < 0 || offset >= forecasts.size()) { return nullptr; } return &forecasts[offset]; } } QString WeatherSummaryFormatter::format(const WeatherReport &report) const { QString message = sourcePrefix(report.location) + ambiguityPrefix(report); if (report.query.kind == WeatherQueryKind::Current) { if (!report.current.valid) { return message + QStringLiteral("天气数据缺少当前天气,暂时无法生成结果。"); } return message + currentText(report); } if (report.query.kind == WeatherQueryKind::Daily) { const WeatherDailyForecast *daily = forecastForOffset(report.dailyForecasts, report.query.dateOffset); if (daily == nullptr) { return message + QStringLiteral("天气数据缺少目标日期预报,暂时无法生成结果。"); } const QString dayName = report.query.dateOffset == 0 ? QStringLiteral("今天") : (report.query.dateOffset == 1 ? QStringLiteral("明天") : QStringLiteral("后天")); return message + QStringLiteral("%1%2天气:%3。") .arg(locationDisplayName(report.location), dayName, dailyText(*daily, daily->date.toString(QStringLiteral("MM-dd"))).section(QStringLiteral(":"), 1)); } QStringList lines; const int count = qMin(report.query.forecastDays, report.dailyForecasts.size()); for (int index = 0; index < count; ++index) { const WeatherDailyForecast &daily = report.dailyForecasts.at(index); lines.append(dailyText(daily, daily.date.toString(QStringLiteral("MM-dd")))); } if (lines.isEmpty()) { return message + QStringLiteral("天气数据缺少未来预报,暂时无法生成结果。"); } return message + QStringLiteral("%1未来 %2 天天气:\n%3。") .arg(locationDisplayName(report.location), QString::number(count), lines.join(QStringLiteral("\n"))); } QString WeatherSummaryFormatter::weatherCodeText(int code) { switch (code) { case 0: return QStringLiteral("晴"); case 1: return QStringLiteral("大部晴朗"); case 2: return QStringLiteral("局部多云"); case 3: return QStringLiteral("阴"); case 45: case 48: return QStringLiteral("雾"); case 51: case 53: case 55: return QStringLiteral("毛毛雨"); case 56: case 57: return QStringLiteral("冻毛毛雨"); case 61: return QStringLiteral("小雨"); case 63: return QStringLiteral("中雨"); case 65: return QStringLiteral("大雨"); case 66: case 67: return QStringLiteral("冻雨"); case 71: return QStringLiteral("小雪"); case 73: return QStringLiteral("中雪"); case 75: return QStringLiteral("大雪"); case 77: return QStringLiteral("雪粒"); case 80: return QStringLiteral("小阵雨"); case 81: return QStringLiteral("中阵雨"); case 82: return QStringLiteral("强阵雨"); case 85: case 86: return QStringLiteral("阵雪"); case 95: return QStringLiteral("雷暴"); case 96: case 99: return QStringLiteral("雷暴伴冰雹"); default: return QStringLiteral("未知天气"); } } QString WeatherSummaryFormatter::windDirectionText(double degree) { const QStringList directions = { QStringLiteral("北风"), QStringLiteral("东北风"), QStringLiteral("东风"), QStringLiteral("东南风"), QStringLiteral("南风"), QStringLiteral("西南风"), QStringLiteral("西风"), QStringLiteral("西北风"), }; const int index = static_cast(qRound(degree / 45.0)) % directions.size(); return directions.at(index); }