weather forecast (Zambretti forecast)

input:

msg.payload.wind_dir - wind direction 0..360

msg.payload.wind_speed - wind speed 0..N (if 0 - calm)

msg.payload.trend- [1014, 1014, 1015, 1017] - array with pressure last 3h

wind_dir / wind_speed - can be optional

output: msg.payload = ['text weather forecast', ZambrettiCode]

ZambrettiCode index is

"Settled fine", "Fine weather", "Becoming fine", "Fine, becoming less settled", "Fine, possible showers", "Fairly fine, improving", "Fairly fine, possible showers early", "Fairly fine, showery later", "Showery early, improving", "Changeable, mending", "Fairly fine, showers likely", "Rather unsettled clearing later", "Unsettled, probably improving", "Showery, bright intervals", "Showery, becoming less settled", "Changeable, some rain", "Unsettled, short fine intervals", "Unsettled, rain later", "Unsettled, some rain", "Mostly very unsettled", "Occasional rain, worsening", "Rain at times, very unsettled", "Rain at frequent intervals", "Rain, very unsettled", "Stormy, may improve", "Stormy, much rain"
[{"id":"80ec1a2f.885258","type":"function","z":"3883ea65.10c676","name":"forecast","func":"// usage:   forecast = betel_cast( z_hpa, z_month, z_wind, z_trend [, z_where] [, z_baro_top] [, z_baro_bottom])[0];\n\n// z_hpa is Sea Level Adjusted (Relative) barometer in hPa or mB\n// z_month is current month as a number between 1 to 12\n// z_wind is English windrose cardinal eg. N, NNW, NW etc.\n// NB. if calm a 'nonsense' value should be sent as z_wind (direction) eg. 1 or calm !\n// z_trend is barometer trend: 0 = no change, 1= rise, 2 = fall\n// z_where - OPTIONAL for posting with form\n// z_baro_top - OPTIONAL for posting with form\n// z_baro_bottom - OPTIONAL for posting with form\n// [0] a short forecast text is returned\n// [1] zambretti severity number (0 - 25) is returned ie. betel_cast() returns a two deep array\n\n// ---- MAIN FUNCTION --------------------------------------------------\nfunction betel_cast(z_hpa, z_month, z_wind, z_trend, z_hemisphere, z_upper, z_lower) {\n\tvar z_where = 1;  // Northern = 1 or Southern = 2 hemisphere\n\tvar z_baro_top = 1050;\t// upper limits of your local 'weather window' (1050.0 hPa for UK)\n\tvar z_baro_bottom = 950;\t// lower limits of your local 'weather window' (950.0 hPa for UK)\n\n\t// equivalents of Zambretti 'dial window' letters A - Z\n\tvar rise_options = new Array(25, 25, 25, 24, 24, 19, 16, 12, 11, 9, 8, 6, 5, 2, 1, 1, 0, 0, 0, 0, 0, 0);\n\tvar steady_options = new Array(25, 25, 25, 25, 25, 25, 23, 23, 22, 18, 15, 13, 10, 4, 1, 1, 0, 0, 0, 0, 0, 0);\n\tvar fall_options = new Array(25, 25, 25, 25, 25, 25, 25, 25, 23, 23, 21, 20, 17, 14, 7, 3, 1, 1, 1, 0, 0, 0);\n\n\tvar z_forecast = new Array(\"Settled fine\", \"Fine weather\", \"Becoming fine\", \"Fine, becoming less settled\", \"Fine, possible showers\", \"Fairly fine, improving\", \"Fairly fine, possible showers early\", \"Fairly fine, showery later\", \"Showery early, improving\", \"Changeable, mending\", \"Fairly fine, showers likely\", \"Rather unsettled clearing later\", \"Unsettled, probably improving\", \"Showery, bright intervals\", \"Showery, becoming less settled\", \"Changeable, some rain\", \"Unsettled, short fine intervals\", \"Unsettled, rain later\", \"Unsettled, some rain\", \"Mostly very unsettled\", \"Occasional rain, worsening\", \"Rain at times, very unsettled\", \"Rain at frequent intervals\", \"Rain, very unsettled\", \"Stormy, may improve\", \"Stormy, much rain\");\n\n\tif (z_hemisphere) z_where = z_hemisphere;\t// used by input form\n\tif (z_upper) z_baro_top = z_upper;\t// used by input form\n\tif (z_lower) z_baro_bottom = z_lower; \t// used by input form\n\tz_range = z_baro_top - z_baro_bottom;\n\tz_constant = (z_range / 22).toFixed(3);\n\n\tz_season = (z_month >= 4 && z_month <= 9); \t// true if 'Summer'\n\tif (z_where == 1) {  \t\t// North hemisphere\n\t\tif (z_wind == \"N\") {\n\t\t\tz_hpa += 6 / 100 * z_range;\n\t\t} else if (z_wind == \"NNE\") {\n\t\t\tz_hpa += 5 / 100 * z_range;\n\t\t} else if (z_wind == \"NE\") {\n\t\t\t//\t\t\tz_hpa += 4 ;  \n\t\t\tz_hpa += 5 / 100 * z_range;\n\t\t} else if (z_wind == \"ENE\") {\n\t\t\tz_hpa += 2 / 100 * z_range;\n\t\t} else if (z_wind == \"E\") {\n\t\t\tz_hpa -= 0.5 / 100 * z_range;\n\t\t} else if (z_wind == \"ESE\") {\n\t\t\t//\t\t\tz_hpa -= 3 ;  \n\t\t\tz_hpa -= 2 / 100 * z_range;\n\t\t} else if (z_wind == \"SE\") {\n\t\t\tz_hpa -= 5 / 100 * z_range;\n\t\t} else if (z_wind == \"SSE\") {\n\t\t\tz_hpa -= 8.5 / 100 * z_range;\n\t\t} else if (z_wind == \"S\") {\n\t\t\t//\t\t\tz_hpa -= 11 ;  \n\t\t\tz_hpa -= 12 / 100 * z_range;\n\t\t} else if (z_wind == \"SSW\") {\n\t\t\tz_hpa -= 10 / 100 * z_range;  //\n\t\t} else if (z_wind == \"SW\") {\n\t\t\tz_hpa -= 6 / 100 * z_range;\n\t\t} else if (z_wind == \"WSW\") {\n\t\t\tz_hpa -= 4.5 / 100 * z_range;  //\n\t\t} else if (z_wind == \"W\") {\n\t\t\tz_hpa -= 3 / 100 * z_range;\n\t\t} else if (z_wind == \"WNW\") {\n\t\t\tz_hpa -= 0.5 / 100 * z_range;\n\t\t} else if (z_wind == \"NW\") {\n\t\t\tz_hpa += 1.5 / 100 * z_range;\n\t\t} else if (z_wind == \"NNW\") {\n\t\t\tz_hpa += 3 / 100 * z_range;\n\t\t}\n\t\tif (z_season == 1) {  \t// if Summer\n\t\t\tif (z_trend == 1) {  \t// rising\n\t\t\t\tz_hpa += 7 / 100 * z_range;\n\t\t\t} else if (z_trend == 2) {  //\tfalling\n\t\t\t\tz_hpa -= 7 / 100 * z_range;\n\t\t\t}\n\t\t}\n\t} else {  \t// must be South hemisphere\n\t\tif (z_wind == \"S\") {\n\t\t\tz_hpa += 6 / 100 * z_range;\n\t\t} else if (z_wind == \"SSW\") {\n\t\t\tz_hpa += 5 / 100 * z_range;\n\t\t} else if (z_wind == \"SW\") {\n\t\t\t//\t\t\tz_hpa += 4 ;  \n\t\t\tz_hpa += 5 / 100 * z_range;\n\t\t} else if (z_wind == \"WSW\") {\n\t\t\tz_hpa += 2 / 100 * z_range;\n\t\t} else if (z_wind == \"W\") {\n\t\t\tz_hpa -= 0.5 / 100 * z_range;\n\t\t} else if (z_wind == \"WNW\") {\n\t\t\t//\t\t\tz_hpa -= 3 ;  \n\t\t\tz_hpa -= 2 / 100 * z_range;\n\t\t} else if (z_wind == \"NW\") {\n\t\t\tz_hpa -= 5 / 100 * z_range;\n\t\t} else if (z_wind == \"NNW\") {\n\t\t\tz_hpa -= 8.5 / 100 * z_range;\n\t\t} else if (z_wind == \"N\") {\n\t\t\t//\t\t\tz_hpa -= 11 ;  \n\t\t\tz_hpa -= 12 / 100 * z_range;\n\t\t} else if (z_wind == \"NNE\") {\n\t\t\tz_hpa -= 10 / 100 * z_range;  //\n\t\t} else if (z_wind == \"NE\") {\n\t\t\tz_hpa -= 6 / 100 * z_range;\n\t\t} else if (z_wind == \"ENE\") {\n\t\t\tz_hpa -= 4.5 / 100 * z_range;  //\n\t\t} else if (z_wind == \"E\") {\n\t\t\tz_hpa -= 3 / 100 * z_range;\n\t\t} else if (z_wind == \"ESE\") {\n\t\t\tz_hpa -= 0.5 / 100 * z_range;\n\t\t} else if (z_wind == \"SE\") {\n\t\t\tz_hpa += 1.5 / 100 * z_range;\n\t\t} else if (z_wind == \"SSE\") {\n\t\t\tz_hpa += 3 / 100 * z_range;\n\t\t}\n\t\tif (z_season === 0) { \t// if Winter\n\t\t\tif (z_trend == 1) {  // rising\n\t\t\t\tz_hpa += 7 / 100 * z_range;\n\t\t\t} else if (z_trend == 2) {  // falling\n\t\t\t\tz_hpa -= 7 / 100 * z_range;\n\t\t\t}\n\t\t}\n\t} \t// END North / South\n\n\tif (z_hpa == z_baro_top) z_hpa = z_baro_top - 1;\n\tz_option = Math.floor((z_hpa - z_baro_bottom) / z_constant);\n\tz_output = \"\";\n\tif (z_option < 0) {\n\t\tz_option = 0;\n\t\tz_output = \"Exceptional Weather, \";\n\t}\n\tif (z_option > 21) {\n\t\tz_option = 21;\n\t\tz_output = \"Exceptional Weather, \";\n\t}\n\n\tvar z_test = [];\n\tif (z_trend == 1) { \t// rising\n\t\tz_output += z_forecast[rise_options[z_option]];\n\t\tz_test[1] = rise_options[z_option];\n\t} else if (z_trend == 2) { \t// falling\n\t\tz_output += z_forecast[fall_options[z_option]];\n\t\tz_test[1] = fall_options[z_option];\n\t} else { \t// must be 'steady'\n\t\tz_output += z_forecast[steady_options[z_option]];\n\t\tz_test[1] = steady_options[z_option];\n\t}\n\t//\treturn z_output ; \n\tz_test[0] = z_output;\n\treturn z_test;\n}\t// END function   \t\t\n\n/**\n * @param {number} y\n * @param {number} x\n * @returns {(number|Array)} lr.slope lr.intercept lr.r2 score\n */\nfunction linearRegression(y, x) {\n    var lr = {};\n    var n = y.length;\n    var sum_x = 0;\n    var sum_y = 0;\n    var sum_xy = 0;\n    var sum_xx = 0;\n    var sum_yy = 0;\n\n    for (var i = 0; i < y.length; i++) {\n\n        sum_x += x[i];\n        sum_y += y[i];\n        sum_xy += (x[i] * y[i]);\n        sum_xx += (x[i] * x[i]);\n        sum_yy += (y[i] * y[i]);\n    }\n\n    lr['slope'] = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);\n    lr['intercept'] = (sum_y - lr.slope * sum_x) / n;\n    lr['slope'] = lr['slope'] | 0;\n    lr['r2'] = Math.pow((n * sum_xy - sum_x * sum_y) / Math.sqrt((n * sum_xx - sum_x * sum_x) * (n * sum_yy - sum_y * sum_y)), 2);\n\n    return lr;\n}\n\nfunction windToRose(wind_dir, wind_speed) {\n    const windDir = [\"N\", \"NNE\", \"NE\", \"ENE\", \"E\", \"ESE\", \"SE\", \"SSE\", \"S\", \"SSW\", \"SW\", \"WSW\", \"W\", \"WNW\", \"NW\", \"NNW\", \"CALM\"];\n    if (wind_dir && wind_speed) {\n        if (wind_speed > 1) {\n            wind_dir = Math.floor((wind_dir / 22.5) + 0.5) % 16;\n            return windDir[wind_dir];\n        }\n    }\n    return \"calm\";\n}\n\nmsg.payload.wind_dir = msg.payload.wind_dir || 'calm';\nmsg.payload.wind_speed = msg.payload.wind_speed || 0;\nmsg.payload.trend = msg.payload.trend || [];\n\nvar z_hpa = msg.payload.trend[msg.payload.trend.length-1];\nvar d = new Date();\nvar z_month = d.getMonth() + 1;\nvar z_wind = windToRose(msg.payload.wind_dir, msg.payload.wind_speed);\n\nvar known_x = msg.payload.trend;\nvar known_y = [...known_x.keys()];\n\nvar lr = linearRegression(known_y, known_x);\n// node.warn(lr);\n\nvar z_trend = 0;\n\nif (Math.round(lr.slope) === 0) z_trend = 0\nelse if (Math.round(lr.slope) > 0) z_trend = 1;\nelse if (Math.round(lr.slope) < 0) z_trend = 2;\n\n//node.warn([z_hpa, z_month, z_wind, z_trend, lr]);\nvar forecast = {}\nforecast.payload = betel_cast(z_hpa, z_month, z_wind, z_trend);    \nreturn forecast;\n","outputs":1,"noerr":0,"x":520,"y":160,"wires":[["47b83a69.4bb6a4","30897d33.031fe2"]]}]

Flow Info

Created 5 years, 10 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • function (x1)

Tags

  • weatherforecast
  • Zambretti
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option