]> git.basschouten.com Git - openhab-addons.git/blob
21f05dc296a8bb24746e1460e0157cdcb61d7b4a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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;
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     // Northern Polar Zone & Northern Tropical Zone
80     private final static String[] NPZDIRECTIONS = { "S", "SW", "W", "NW", "N", "NE", "E", "SE" };
81     // Northern Temperate Zone
82     private final static String[] NTZDIRECTIONS = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" };
83     // Southern Polar Zone & Southern Tropical Zone
84     private final static String[] SPZDIRECTIONS = { "N", "NW", "W", "SW", "S", "SE", "E", "NE" };
85     // Southern Temperate Zone
86     private final static String[] STZDIRECTIONS = { "S", "SE", "E", "NE", "N", "NW", "W", "SW" };
87
88     private final Logger logger = LoggerFactory.getLogger(SagerWeatherCaster.class);
89     private final Properties forecaster = new Properties();
90
91     private Optional<Prevision> prevision = Optional.empty();
92     private String[] usedDirections = NTZDIRECTIONS; // Defaulted to Northern Zone
93
94     private int currentBearing = -1;
95     private int windEvolution = -1; // Whether the wind during the last 6 hours has changed its direction by
96                                     // approximately 45 degrees or more
97     private int sagerPressure = -1; // currentPressure is Sea Level Adjusted (Relative) barometer in hPa or mB
98     private int pressureEvolution = -1; // pressureEvolution There are five points for registering the behavior of your
99     // barometer for a period of about 6 hours prior to the forecast.
100     private int nubes = -1;
101     private int currentBeaufort = -1;
102     private double cloudLevel = -1;
103     private boolean raining = false;
104
105     @Activate
106     public SagerWeatherCaster() {
107         try (InputStream input = Thread.currentThread().getContextClassLoader()
108                 .getResourceAsStream("/sagerForecaster.properties")) {
109             forecaster.load(input);
110         } catch (IOException e) {
111             logger.warn("Error during Sager Forecaster startup", e);
112         }
113     }
114
115     public String[] getUsedDirections() {
116         return usedDirections;
117     }
118
119     public void setBearing(int newBearing, int oldBearing) {
120         int windEvol = sagerWindTrend(oldBearing, newBearing);
121         if ((windEvol != windEvolution) || (newBearing != currentBearing)) {
122             currentBearing = newBearing;
123             windEvolution = windEvol;
124             updatePrediction();
125         }
126     }
127
128     public void setPressure(double newPressure, double oldPressure) {
129         int newSagerPressure = sagerPressureLevel(newPressure);
130         int pressEvol = sagerPressureTrend(newPressure, oldPressure);
131         if ((pressEvol != pressureEvolution) || (newSagerPressure != sagerPressure)) {
132             sagerPressure = newSagerPressure;
133             pressureEvolution = pressEvol;
134             updatePrediction();
135         }
136     }
137
138     public void setCloudLevel(int cloudiness) {
139         cloudLevel = cloudiness;
140         sagerNubesUpdate();
141     }
142
143     public void setRaining(boolean isRaining) {
144         raining = isRaining;
145         sagerNubesUpdate();
146     }
147
148     public void setBeaufort(int beaufortIndex) {
149         if (currentBeaufort != beaufortIndex) {
150             currentBeaufort = beaufortIndex;
151             updatePrediction();
152         }
153     }
154
155     public int getBeaufort() {
156         return currentBeaufort;
157     }
158
159     public int getWindEvolution() {
160         return windEvolution;
161     }
162
163     public int getPressureEvolution() {
164         return pressureEvolution;
165     }
166
167     private void sagerNubesUpdate() {
168         int result;
169         if (!raining) {
170             if (cloudLevel > 80) {
171                 result = 4; // overcast
172             } else if (cloudLevel > 50) {
173                 result = 3; // mostly overcast
174             } else if (cloudLevel > 20) {
175                 result = 2; // partly cloudy
176             } else {
177                 result = 1; // clear
178             }
179         } else {
180             result = 5; // raining
181         }
182         if (result != nubes) {
183             nubes = result;
184             updatePrediction();
185         }
186     }
187
188     private static int sagerPressureLevel(double current) {
189         if (current > 1029.46) {
190             return 1;
191         } else if (current > 1019.3) {
192             return 2;
193         } else if (current > 1012.53) {
194             return 3;
195         } else if (current > 1005.76) {
196             return 4;
197         } else if (current > 999) {
198             return 5;
199         } else if (current > 988.8) {
200             return 6;
201         } else if (current > 975.28) {
202             return 7;
203         }
204         return 8;
205     }
206
207     private static int sagerPressureTrend(double current, double historic) {
208         double evol = current - historic;
209
210         if (evol > 1.4) {
211             return 1; // Rising Rapidly
212         } else if (evol > 0.68) {
213             return 2; // Rising Slowly
214         } else if (evol > -0.68) {
215             return 3; // Normal
216         } else if (evol > -1.4) {
217             return 4; // Decreasing Slowly
218         }
219         return 5; // Decreasing Rapidly
220     }
221
222     private static int sagerWindTrend(double historic, double position) {
223         int result = 1; // Steady
224         double angle = 180 - Math.abs(Math.abs(position - historic) - 180);
225         if (angle > 45) {
226             int evol = (int) (historic + angle);
227             evol -= (evol > 360) ? 360 : 0;
228             result = (evol == position) ? 2 : 3; // Veering : Backing
229         }
230         return result;
231     }
232
233     private String getCompass() {
234         double step = 360.0 / NTZDIRECTIONS.length;
235         double b = Math.floor((currentBearing + (step / 2.0)) / step);
236         return NTZDIRECTIONS[(int) (b % NTZDIRECTIONS.length)];
237     }
238
239     private void updatePrediction() {
240         int zWind = Arrays.asList(usedDirections).indexOf(getCompass());
241         String d1 = "-";
242         switch (zWind) {
243             case 0:
244                 if (windEvolution == 3) {
245                     d1 = "A";
246                 } else if (windEvolution == 1) {
247                     d1 = "B";
248                 } else if (windEvolution == 2) {
249                     d1 = "C";
250                 }
251                 break;
252             case 1:
253                 if (windEvolution == 3) {
254                     d1 = "D";
255                 } else if (windEvolution == 1) {
256                     d1 = "E";
257                 } else if (windEvolution == 2) {
258                     d1 = "F";
259                 }
260                 break;
261             case 2:
262                 if (windEvolution == 3) {
263                     d1 = "G";
264                 } else if (windEvolution == 1) {
265                     d1 = "H";
266                 } else if (windEvolution == 2) {
267                     d1 = "J";
268                 }
269                 break;
270             case 3:
271                 if (windEvolution == 3) {
272                     d1 = "K";
273                 } else if (windEvolution == 1) {
274                     d1 = "L";
275                 } else if (windEvolution == 2) {
276                     d1 = "M";
277                 }
278                 break;
279             case 4:
280                 if (windEvolution == 3) {
281                     d1 = "N";
282                 } else if (windEvolution == 1) {
283                     d1 = "O";
284                 } else if (windEvolution == 2) {
285                     d1 = "P";
286                 }
287                 break;
288             case 5:
289                 if (windEvolution == 3) {
290                     d1 = "Q";
291                 } else if (windEvolution == 1) {
292                     d1 = "R";
293                 } else if (windEvolution == 2) {
294                     d1 = "S";
295                 }
296                 break;
297             case 6:
298                 if (windEvolution == 3) {
299                     d1 = "T";
300                 } else if (windEvolution == 1) {
301                     d1 = "U";
302                 } else if (windEvolution == 2) {
303                     d1 = "V";
304                 }
305                 break;
306             case 7:
307                 if (windEvolution == 3) {
308                     d1 = "W";
309                 } else if (windEvolution == 1) {
310                     d1 = "X";
311                 } else if (windEvolution == 2) {
312                     d1 = "Y";
313                 }
314                 break;
315             default:
316                 if (currentBeaufort == 0) {
317                     d1 = "Z";
318                 }
319         }
320         String forecast = forecaster.getProperty(
321                 d1 + String.valueOf(sagerPressure) + String.valueOf(pressureEvolution) + String.valueOf(nubes));
322         prevision = (forecast != null) ? Optional.of(new Prevision(forecast)) : Optional.empty();
323     }
324
325     public String getForecast() {
326         if (prevision.isPresent()) {
327             char forecast = prevision.get().zForecast;
328             return Character.toString(forecast);
329         }
330         return "-";
331     }
332
333     public String getWindVelocity() {
334         if (prevision.isPresent()) {
335             char windVelocity = prevision.get().zWindVelocity;
336             return Character.toString(windVelocity);
337         }
338         return "-";
339     }
340
341     public String getWindDirection() {
342         if (prevision.isPresent()) {
343             int direction = prevision.get().zWindDirection;
344             return String.valueOf(direction);
345         }
346         return "-";
347     }
348
349     public String getWindDirection2() {
350         if (prevision.isPresent()) {
351             int direction = prevision.get().zWindDirection2;
352             return String.valueOf(direction);
353         }
354         return "-";
355     }
356
357     public void setLatitude(double latitude) {
358         if (latitude >= 66.6) {
359             usedDirections = NPZDIRECTIONS;
360         } else if (latitude >= 23.5) {
361             usedDirections = NTZDIRECTIONS;
362         } else if (latitude >= 0) {
363             usedDirections = NPZDIRECTIONS;
364         } else if (latitude > -23.5) {
365             usedDirections = SPZDIRECTIONS;
366         } else if (latitude > -66.6) {
367             usedDirections = STZDIRECTIONS;
368         } else {
369             usedDirections = SPZDIRECTIONS;
370         }
371     }
372
373     private class Prevision {
374         public final char zForecast;
375         public final char zWindVelocity;
376         public final int zWindDirection;
377         public final int zWindDirection2;
378
379         public Prevision(String forecast) {
380             zForecast = forecast.charAt(0);
381             zWindVelocity = forecast.charAt(1);
382             zWindDirection = Character.getNumericValue(forecast.charAt(2));
383             zWindDirection2 = (forecast.length() > 3) ? Character.getNumericValue(forecast.charAt(3)) : -1;
384         }
385     }
386 }