]> git.basschouten.com Git - openhab-addons.git/blob
cbe752830c7d5b16c7c78b8dada9a285abf33ca0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 /**
14  *       eltiempo.selfip.com - Sager Weathercaster Algorhithm
15  *
16  *          Copyright © 2008 Naish666 (eltiempo.selfip.com)
17  *               October 2008 - v1.0
18  *          Java transposition done by Gaël L'hopital - 2015
19  **
20  *     This program is free software: you can redistribute it and/or modify
21  *    it under the terms of the GNU General Public License as published by
22  *    the Free Software Foundation, either version 3 of the License, or
23  *    (at your option) any later version.
24  *
25  *    This program is distributed in the hope that it will be useful,
26  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
27  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28  *    GNU General Public License for more details.
29  *
30  *    You should have received a copy of the GNU General Public License
31  *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
32  *
33  ***
34  * BT's Global Sager Weathercaster PHP Scripts For Cumulus (Weathercaster)
35  * by "Buford T. Justice" / "BTJustice"
36  * http://www.freewebs.com/btjustice/bt-forecasters.html
37  * 2014-02-05
38  *
39  * You may redistribute and use these PHP Scripts any way you wish as long as
40  * they remain FREE and money is not charged for their use directly or indirectly.
41  * If these PHP Scripts are used in your work or are modified in any way, please
42  * retain the full credit header.
43  * Based Upon:
44  * The Sager Weathercaster:  A Scientific Instrument for Accurate Prediction of
45  * the Weather
46  * Copyright © 1969 by Raymond M. Sager and E. F. Sager
47  " The Sager Weathercaster predicts the weather quickly and accurately.  It has been
48  * in use since 1942.
49  * Not a novelty, not a toy, this is a highly dependable, scientifically designed
50  * tool of inestimable value to travelers, farmers, hunters, sailors, yachtsmen, campers,
51  * fishermen, students -- in fact, to everyone who needs or wants to know what
52  * the weather will be."
53  * 378 possible forecasts determined from 4996 dial codes.
54  */
55
56 package org.openhab.binding.sagercaster.internal.caster;
57
58 import java.io.IOException;
59 import java.io.InputStream;
60 import java.util.Arrays;
61 import java.util.Optional;
62 import java.util.Properties;
63
64 import org.eclipse.jdt.annotation.NonNullByDefault;
65 import org.osgi.service.component.annotations.Activate;
66 import org.osgi.service.component.annotations.Component;
67 import org.osgi.service.component.annotations.ServiceScope;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 /**
72  * This class is responsible for handling the SagerWeatherCaster algorithm
73  *
74  * @author Gaël L'hopital - Initial contribution
75  */
76 @Component(service = SagerWeatherCaster.class, scope = ServiceScope.SINGLETON)
77 @NonNullByDefault
78 public class SagerWeatherCaster {
79     public static final String UNDEF = "-";
80     // Northern Polar Zone & Northern Tropical Zone
81     private static final String[] NPZDIRECTIONS = { "S", "SW", "W", "NW", "N", "NE", "E", "SE" };
82     // Northern Temperate Zone
83     private static final String[] NTZDIRECTIONS = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" };
84     // Southern Polar Zone & Southern Tropical Zone
85     private static final String[] SPZDIRECTIONS = { "N", "NW", "W", "SW", "S", "SE", "E", "NE" };
86     // Southern Temperate Zone
87     private static final String[] STZDIRECTIONS = { "S", "SE", "E", "NE", "N", "NW", "W", "SW" };
88
89     private final Logger logger = LoggerFactory.getLogger(SagerWeatherCaster.class);
90     private final Properties forecaster = new Properties();
91
92     private Optional<SagerPrediction> prevision = Optional.empty();
93     private String[] usedDirections = NTZDIRECTIONS; // Defaulted to Northern Zone
94
95     private int currentBearing = -1;
96     private int windEvolution = -1; // Whether the wind during the last 6 hours has changed its direction by
97                                     // approximately 45 degrees or more
98     private int sagerPressure = -1; // currentPressure is Sea Level Adjusted (Relative) barometer in hPa or mB
99     private int pressureEvolution = -1; // pressureEvolution There are five points for registering the behavior of your
100     // barometer for a period of about 6 hours prior to the forecast.
101     private int nubes = -1;
102     private int currentBeaufort = -1;
103     private double cloudLevel = -1;
104     private boolean raining = false;
105
106     @Activate
107     public SagerWeatherCaster() {
108         try (InputStream input = Thread.currentThread().getContextClassLoader()
109                 .getResourceAsStream("/sagerForecaster.properties")) {
110             forecaster.load(input);
111         } catch (IOException e) {
112             logger.warn("Error during Sager Forecaster startup", e);
113         }
114     }
115
116     public String[] getUsedDirections() {
117         return usedDirections;
118     }
119
120     public void setBearing(int newBearing, int oldBearing) {
121         int windEvol = sagerWindTrend(oldBearing, newBearing);
122         if ((windEvol != windEvolution) || (newBearing != currentBearing)) {
123             currentBearing = newBearing;
124             windEvolution = windEvol;
125             updatePrediction();
126         }
127     }
128
129     public void setPressure(double newPressure, double oldPressure) {
130         int newSagerPressure = sagerPressureLevel(newPressure);
131         int pressEvol = sagerPressureTrend(newPressure, oldPressure);
132         if ((pressEvol != pressureEvolution) || (newSagerPressure != sagerPressure)) {
133             sagerPressure = newSagerPressure;
134             pressureEvolution = pressEvol;
135             updatePrediction();
136         }
137     }
138
139     public void setCloudLevel(int cloudiness) {
140         cloudLevel = cloudiness;
141         sagerNubesUpdate();
142     }
143
144     public void setRaining(boolean isRaining) {
145         raining = isRaining;
146         sagerNubesUpdate();
147     }
148
149     public void setBeaufort(int beaufortIndex) {
150         if (currentBeaufort != beaufortIndex) {
151             currentBeaufort = beaufortIndex;
152             updatePrediction();
153         }
154     }
155
156     public int getBeaufort() {
157         return currentBeaufort;
158     }
159
160     public int getWindEvolution() {
161         return windEvolution;
162     }
163
164     public int getPressureEvolution() {
165         return pressureEvolution;
166     }
167
168     private void sagerNubesUpdate() {
169         int result;
170         if (!raining) {
171             if (cloudLevel > 80) {
172                 result = 4; // overcast
173             } else if (cloudLevel > 50) {
174                 result = 3; // mostly overcast
175             } else if (cloudLevel > 20) {
176                 result = 2; // partly cloudy
177             } else {
178                 result = 1; // clear
179             }
180         } else {
181             result = 5; // raining
182         }
183         if (result != nubes) {
184             nubes = result;
185             updatePrediction();
186         }
187     }
188
189     private static int sagerPressureLevel(double current) {
190         if (current > 1029.46) {
191             return 1;
192         } else if (current > 1019.3) {
193             return 2;
194         } else if (current > 1012.53) {
195             return 3;
196         } else if (current > 1005.76) {
197             return 4;
198         } else if (current > 999) {
199             return 5;
200         } else if (current > 988.8) {
201             return 6;
202         } else if (current > 975.28) {
203             return 7;
204         }
205         return 8;
206     }
207
208     private static int sagerPressureTrend(double current, double historic) {
209         double evol = current - historic;
210
211         if (evol > 1.4) {
212             return 1; // Rising Rapidly
213         } else if (evol > 0.68) {
214             return 2; // Rising Slowly
215         } else if (evol > -0.68) {
216             return 3; // Normal
217         } else if (evol > -1.4) {
218             return 4; // Decreasing Slowly
219         }
220         return 5; // Decreasing Rapidly
221     }
222
223     private static int sagerWindTrend(double historic, double position) {
224         int result = 1; // Steady
225         double angle = 180 - Math.abs(Math.abs(position - historic) - 180);
226         if (angle > 45) {
227             int evol = (int) (historic + angle);
228             evol -= (evol > 360) ? 360 : 0;
229             result = (evol == position) ? 2 : 3; // Veering : Backing
230         }
231         return result;
232     }
233
234     private String getCompass() {
235         double step = 360.0 / NTZDIRECTIONS.length;
236         double b = Math.floor((currentBearing + (step / 2.0)) / step);
237         return NTZDIRECTIONS[(int) (b % NTZDIRECTIONS.length)];
238     }
239
240     private void updatePrediction() {
241         int zWind = Arrays.asList(usedDirections).indexOf(getCompass());
242         String d1 = UNDEF;
243         switch (zWind) {
244             case 0:
245                 if (windEvolution == 3) {
246                     d1 = "A";
247                 } else if (windEvolution == 1) {
248                     d1 = "B";
249                 } else if (windEvolution == 2) {
250                     d1 = "C";
251                 }
252                 break;
253             case 1:
254                 if (windEvolution == 3) {
255                     d1 = "D";
256                 } else if (windEvolution == 1) {
257                     d1 = "E";
258                 } else if (windEvolution == 2) {
259                     d1 = "F";
260                 }
261                 break;
262             case 2:
263                 if (windEvolution == 3) {
264                     d1 = "G";
265                 } else if (windEvolution == 1) {
266                     d1 = "H";
267                 } else if (windEvolution == 2) {
268                     d1 = "J";
269                 }
270                 break;
271             case 3:
272                 if (windEvolution == 3) {
273                     d1 = "K";
274                 } else if (windEvolution == 1) {
275                     d1 = "L";
276                 } else if (windEvolution == 2) {
277                     d1 = "M";
278                 }
279                 break;
280             case 4:
281                 if (windEvolution == 3) {
282                     d1 = "N";
283                 } else if (windEvolution == 1) {
284                     d1 = "O";
285                 } else if (windEvolution == 2) {
286                     d1 = "P";
287                 }
288                 break;
289             case 5:
290                 if (windEvolution == 3) {
291                     d1 = "Q";
292                 } else if (windEvolution == 1) {
293                     d1 = "R";
294                 } else if (windEvolution == 2) {
295                     d1 = "S";
296                 }
297                 break;
298             case 6:
299                 if (windEvolution == 3) {
300                     d1 = "T";
301                 } else if (windEvolution == 1) {
302                     d1 = "U";
303                 } else if (windEvolution == 2) {
304                     d1 = "V";
305                 }
306                 break;
307             case 7:
308                 if (windEvolution == 3) {
309                     d1 = "W";
310                 } else if (windEvolution == 1) {
311                     d1 = "X";
312                 } else if (windEvolution == 2) {
313                     d1 = "Y";
314                 }
315                 break;
316             default:
317                 if (currentBeaufort == 0) {
318                     d1 = "Z";
319                 }
320         }
321         String forecast = forecaster.getProperty(d1 + sagerPressure + pressureEvolution + nubes);
322         prevision = Optional.ofNullable(forecast != null ? new SagerPrediction(forecast) : null);
323     }
324
325     public String getForecast() {
326         return prevision.map(p -> p.getForecast()).orElse(UNDEF);
327     }
328
329     public String getWindVelocity() {
330         return prevision.map(p -> p.getWindVelocity()).orElse(UNDEF);
331     }
332
333     public String getWindDirection() {
334         return prevision.map(p -> p.getWindDirection()).orElse(UNDEF);
335     }
336
337     public String getWindDirection2() {
338         return prevision.map(p -> p.getWindDirection2()).orElse(UNDEF);
339     }
340
341     public String getSagerCode() {
342         return prevision.map(p -> p.getSagerCode()).orElse(UNDEF);
343     }
344
345     public void setLatitude(double latitude) {
346         if (latitude >= 66.6) {
347             usedDirections = NPZDIRECTIONS;
348         } else if (latitude >= 23.5) {
349             usedDirections = NTZDIRECTIONS;
350         } else if (latitude >= 0) {
351             usedDirections = NPZDIRECTIONS;
352         } else if (latitude > -23.5) {
353             usedDirections = SPZDIRECTIONS;
354         } else if (latitude > -66.6) {
355             usedDirections = STZDIRECTIONS;
356         } else {
357             usedDirections = SPZDIRECTIONS;
358         }
359     }
360
361     public int getPredictedBeaufort() {
362         int result = currentBeaufort;
363         switch (getWindVelocity()) {
364             case "N":
365                 result += 1;
366                 break;
367             case "F":
368                 result = 4;
369                 break;
370             case "S":
371                 result = 6;
372                 break;
373             case "G":
374                 result = 8;
375                 break;
376             case "W":
377                 result = 10;
378                 break;
379             case "H":
380                 result = 12;
381                 break;
382             case "D":
383                 result -= 1;
384                 break;
385         }
386         return result;
387     }
388 }