#include #include "config.h" #include "forecast.h" // Full Zambretti phrases, index 0='A' .. 25='Z'. static const char* const ZTEXT[26] = { "Settled fine weather", // A "Fine weather", // B "Becoming fine", // C "Fine, becoming less settled", // D "Fine, possibly showers", // E "Fairly fine, improving", // F "Fairly fine, possibly showers early", // G "Fairly fine, showers later", // H "Showery early, improving", // I "Changeable, improving", // J "Fairly fine, showers likely", // K "Rather unsettled, clearing later", // L "Unsettled, probably improving", // M "Showery, bright intervals", // N "Showery, becoming unsettled", // O "Changeable, some rain", // P "Unsettled, short fine intervals", // Q "Unsettled, rain later", // R "Unsettled, rain at times", // S "Very unsettled, finer at times", // T "Rain at times, worse later", // U "Rain at times, becoming very unsettled", // V "Rain at frequent intervals", // W "Very unsettled, rain", // X "Stormy, possibly improving", // Y "Stormy, much rain" // Z }; // Map a constrained Zambretti number to a letter, per trend (G6EJD tables). static char letterRising(int z) { const char* m = "ABCFGIJLMQTYZ"; return m[z - 1]; } // z 1..13 static char letterFalling(int z) { const char* m = "ABDHORUXZ"; return m[z - 1]; } // z 1..9 static char letterSteady(int z) { const char* m = "ABEKNPSWXZ"; return m[z - 1]; } // z 1..10 static int clampi(int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v); } static WxCategory categoryOf(char letter) { switch (letter) { case 'A': case 'B': case 'C': case 'F': return WX_FINE; case 'E': case 'G': case 'I': case 'J': case 'K': case 'M': case 'N': case 'Q': return WX_FAIR; case 'D': case 'H': case 'L': case 'O': case 'P': return WX_CHANGEABLE; case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': return WX_RAIN; case 'Y': case 'Z': return WX_STORM; default: return WX_UNKNOWN; } } Trend classifyTrend(float d) { if (d > TREND_THRESHOLD_HPA) return TREND_RISING; if (d < -TREND_THRESHOLD_HPA) return TREND_FALLING; return TREND_STEADY; } Forecast computeForecast(float p, Trend trend, int month) { bool winter = (month < 4 || month > 9); // northern hemisphere int z; char letter; if (trend == TREND_RISING) { z = (int)lround(-0.1449 * p + 150.18); if (winter) z += 1; z = clampi(z, 1, 13); letter = letterRising(z); } else if (trend == TREND_FALLING) { z = (int)lround(0.0000257935 * p * p * p - 0.078482105 * p * p + 79.4582219457 * p - 26762.7164899421); if (winter) z -= 1; z = clampi(z, 1, 9); letter = letterFalling(z); } else { z = (int)lround(0.0000258964 * p * p * p - 0.07753778137 * p * p + 77.2287820569 * p - 25582.130426005); z = clampi(z, 1, 10); letter = letterSteady(z); } return Forecast{ letter, ZTEXT[letter - 'A'], categoryOf(letter) }; } const char* forecastTextForLetter(char letter) { if (letter < 'A' || letter > 'Z') return ""; return ZTEXT[letter - 'A']; } const char* categoryShort(WxCategory c) { switch (c) { case WX_FINE: return "Fine"; case WX_FAIR: return "Fair"; case WX_CHANGEABLE: return "Changeable"; case WX_RAIN: return "Rain"; case WX_STORM: return "Storm"; default: return "..."; } }