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