]> git.basschouten.com Git - openhab-addons.git/blob
b627fee98ca486feb7d5e938c686490e25a948aa
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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 package org.openhab.binding.meteoblue.internal;
14
15 import java.awt.Color;
16 import java.awt.Image;
17 import java.awt.image.BufferedImage;
18 import java.io.File;
19 import java.io.FileNotFoundException;
20 import java.io.IOException;
21 import java.lang.reflect.Field;
22 import java.text.ParseException;
23 import java.text.SimpleDateFormat;
24 import java.util.Calendar;
25 import java.util.TimeZone;
26
27 import javax.imageio.ImageIO;
28
29 import org.openhab.binding.meteoblue.internal.json.JsonDataDay;
30 import org.openhab.binding.meteoblue.internal.json.JsonMetadata;
31 import org.openhab.binding.meteoblue.internal.json.JsonUnits;
32 import org.openhab.core.OpenHAB;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * Model of forecast data.
38  *
39  * @author Chris Carman - Initial contribution
40  */
41 public class Forecast {
42     private final Logger logger = LoggerFactory.getLogger(Forecast.class);
43
44     // metadata fields
45     private Double latitude;
46     private Double longitude;
47     private Integer height;
48     private String timeZoneAbbreviation;
49
50     // units fields
51     private String timeUnits;
52     private String predictabilityUnits;
53     private String precipitationProbabilityUnits;
54     private String pressureUnits;
55     private String relativeHumidityUnits;
56     private String temperatureUnits;
57     private String windDirectionUnits;
58     private String precipitationUnits;
59     private String windSpeedUnits;
60
61     // data_day fields
62     private Calendar forecastDate;
63     private Integer pictocode;
64     private Integer UVIndex;
65     private Double minTemperature;
66     private Double maxTemperature;
67     private Double meanTemperature;
68     private Double feltTemperatureMin;
69     private Double feltTemperatureMax;
70     private Integer windDirection;
71     private Integer precipitationProbability;
72     private String rainSpot;
73     private Integer predictabilityClass;
74     private Integer predictability;
75     private Double precipitation;
76     private Double snowFraction;
77     private Integer minSeaLevelPressure;
78     private Integer maxSeaLevelPressure;
79     private Integer meanSeaLevelPressure;
80     private Double minWindSpeed;
81     private Double maxWindSpeed;
82     private Double meanWindSpeed;
83     private Integer relativeHumidityMin;
84     private Integer relativeHumidityMax;
85     private Integer relativeHumidityMean;
86     private Double convectivePrecipitation;
87     private Double precipitationHours;
88     private Double humidityGreater90Hours;
89
90     // derived fields
91     private String cardinalWindDirection;
92     private String iconName;
93     private Image icon;
94     private Image rainArea;
95     private Double snowFall;
96
97     public Forecast(int whichDay, JsonMetadata metadata, JsonUnits units, JsonDataDay dataDay)
98             throws IllegalArgumentException {
99         if (metadata == null) {
100             throw new IllegalArgumentException("Received no metadata information.");
101         }
102         if (dataDay == null) {
103             throw new IllegalArgumentException("Received no data_day information.");
104         }
105         if (whichDay > dataDay.getTime().length - 1) {
106             throw new IndexOutOfBoundsException("No data received for day " + whichDay);
107         }
108
109         // extract the metadata fields
110         latitude = metadata.getLatitude();
111         longitude = metadata.getLongitude();
112         height = metadata.getHeight();
113         timeZoneAbbreviation = metadata.getTimeZoneAbbreviation();
114
115         logger.trace("Metadata:");
116         logger.trace("  Latitude: {}", latitude);
117         logger.trace("  Longitude: {}", longitude);
118         logger.trace("  Height: {}", height);
119         logger.trace("  TZ Abbrev: {}", timeZoneAbbreviation);
120
121         // extract the units fields
122         timeUnits = units.getTime();
123         predictabilityUnits = units.getPredictability();
124         precipitationProbabilityUnits = units.getPrecipitationProbability();
125         pressureUnits = units.getPressure();
126         relativeHumidityUnits = units.getRelativeHumidity();
127         temperatureUnits = units.getTemperature();
128         windDirectionUnits = units.getWindDirection();
129         precipitationUnits = units.getPrecipitation();
130         windSpeedUnits = units.getWindSpeed();
131
132         logger.trace("Units:");
133         logger.trace("  Time: {}", timeUnits);
134         logger.trace("  Predictability: {}", predictabilityUnits);
135         logger.trace("  Precipitation Probability: {}", precipitationProbabilityUnits);
136         logger.trace("  Pressure: {}", pressureUnits);
137         logger.trace("  Relative Humidity: {}", relativeHumidityUnits);
138         logger.trace("  Temperature: {}", temperatureUnits);
139         logger.trace("  Wind Direction: {}", windDirectionUnits);
140         logger.trace("  Precipitation: {}", precipitationUnits);
141         logger.trace("  Wind Speed: {}", windSpeedUnits);
142
143         // extract the data_day fields
144         String timeString = dataDay.getTime()[whichDay];
145         try {
146             Calendar c = Calendar.getInstance(TimeZone.getTimeZone(timeZoneAbbreviation));
147             SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
148             c.setTime(formatter.parse(timeString));
149             forecastDate = c;
150         } catch (ParseException e) {
151             logger.debug("Failed to parse the value '{}' as a date.", timeString);
152         }
153
154         pictocode = dataDay.getPictocode()[whichDay];
155         iconName = getIconNameForPictocode(pictocode);
156         icon = loadImageIcon(iconName);
157         UVIndex = dataDay.getUVIndex()[whichDay];
158         maxTemperature = dataDay.getTemperatureMax()[whichDay];
159         minTemperature = dataDay.getTemperatureMin()[whichDay];
160         meanTemperature = dataDay.getTemperatureMean()[whichDay];
161         feltTemperatureMax = dataDay.getFeltTemperatureMax()[whichDay];
162         feltTemperatureMin = dataDay.getFeltTemperatureMin()[whichDay];
163         windDirection = dataDay.getWindDirection()[whichDay];
164         cardinalWindDirection = getCardinalDirection(windDirection);
165         precipitationProbability = dataDay.getPrecipitationProbability()[whichDay];
166         rainSpot = dataDay.getRainspot()[whichDay];
167         rainArea = generateRainAreaImage();
168         predictabilityClass = dataDay.getPredictabilityClass()[whichDay];
169         predictability = dataDay.getPredictability()[whichDay];
170         precipitation = dataDay.getPrecipitation()[whichDay];
171         snowFraction = dataDay.getSnowFraction()[whichDay];
172         maxSeaLevelPressure = dataDay.getSeaLevelPressureMax()[whichDay];
173         minSeaLevelPressure = dataDay.getSeaLevelPressureMin()[whichDay];
174         meanSeaLevelPressure = dataDay.getSeaLevelPressureMean()[whichDay];
175         maxWindSpeed = dataDay.getWindSpeedMax()[whichDay];
176         meanWindSpeed = dataDay.getWindSpeedMean()[whichDay];
177         minWindSpeed = dataDay.getWindSpeedMin()[whichDay];
178         relativeHumidityMax = dataDay.getRelativeHumidityMax()[whichDay];
179         relativeHumidityMin = dataDay.getRelativeHumidityMin()[whichDay];
180         relativeHumidityMean = dataDay.getRelativeHumidityMean()[whichDay];
181         convectivePrecipitation = dataDay.getConvectivePrecipitation()[whichDay];
182         snowFall = snowFraction * precipitation;
183         precipitationHours = dataDay.getPrecipitationHours()[whichDay];
184         humidityGreater90Hours = dataDay.getHumidityGreater90Hours()[whichDay];
185
186         logger.trace("DataDay:");
187         logger.trace("  Time: {}", forecastDate);
188         logger.trace("  Pictocode: {}", pictocode);
189         logger.trace("  Icon: {}", icon);
190         logger.trace("  UV Index: {}", UVIndex);
191         logger.trace("  Max Temperature: {}", maxTemperature);
192         logger.trace("  Min Temperature: {}", minTemperature);
193         logger.trace("  Mean Temperature: {}", meanTemperature);
194         logger.trace("  Felt Temperature Max: {}", feltTemperatureMax);
195         logger.trace("  Felt Temperature Min: {}", feltTemperatureMin);
196         logger.trace("  Wind Direction: {}", windDirection);
197         logger.trace("  Precipitation Probability: {}", precipitationProbability);
198         logger.trace("  Rainspot: {}", rainSpot);
199         logger.trace("  Rain Area: {}", rainArea);
200         logger.trace("  Predictability Class: {}", predictabilityClass);
201         logger.trace("  Predictability: {}", predictability);
202         logger.trace("  Precipitation: {}", precipitation);
203         logger.trace("  Snow Fraction: {}", snowFraction);
204         logger.trace("  Max Sea-level Pressure: {}", maxSeaLevelPressure);
205         logger.trace("  Min Sea-level Pressure: {}", minSeaLevelPressure);
206         logger.trace("  Mean Sea-level Pressure: {}", meanSeaLevelPressure);
207         logger.trace("  Max Wind Speed: {}", maxWindSpeed);
208         logger.trace("  Mean Wind Speed: {}", meanWindSpeed);
209         logger.trace("  Min Wind Speed: {}", minWindSpeed);
210         logger.trace("  Relative Humidity Max: {}", relativeHumidityMax);
211         logger.trace("  Relative Humidity Min: {}", relativeHumidityMin);
212         logger.trace("  Relative Humidity Mean: {}", relativeHumidityMean);
213         logger.trace("  Convective Precipitation: {}", convectivePrecipitation);
214         logger.trace("  Snowfall: {}", snowFall);
215         logger.trace("  Precipitation Hours: {}", precipitationHours);
216         logger.trace("  Humidity > 90 Hours: {}", humidityGreater90Hours);
217     }
218
219     // generic getter
220     public Object getDatapoint(String datapointName) {
221         if ("condition".equals(datapointName)) {
222             return String.valueOf(pictocode);
223         }
224
225         try {
226             Field field = getClass().getDeclaredField(datapointName);
227             field.setAccessible(true);
228             return field.get(this);
229         } catch (NoSuchFieldException e) {
230             logger.warn("Unable to find a datapoint declared for the name '{}'", datapointName);
231             return null;
232         } catch (Exception e) {
233             logger.warn("An unexpected error occurred while trying to access the datapoint '{}'", datapointName, e);
234             return null;
235         }
236     }
237
238     // metadata getters
239     public Integer getHeight() {
240         return height;
241     }
242
243     // units getters
244     public String getTimeUnits() {
245         return timeUnits;
246     }
247
248     public String getPredictabilityUnits() {
249         return predictabilityUnits;
250     }
251
252     public String getPrecipitationProbabilityUnits() {
253         return precipitationProbabilityUnits;
254     }
255
256     public String getPressureUnits() {
257         return pressureUnits;
258     }
259
260     public String getRelativeHumidityUnits() {
261         return relativeHumidityUnits;
262     }
263
264     public String getTemperatureUnits() {
265         return temperatureUnits;
266     }
267
268     public String getWindDirectionUnits() {
269         return windDirectionUnits;
270     }
271
272     public String getPrecipitationUnits() {
273         return precipitationUnits;
274     }
275
276     public String getWindSpeedUnits() {
277         return windSpeedUnits;
278     }
279
280     // data_day getters
281     public Calendar getForecastDate() {
282         return forecastDate;
283     }
284
285     public Integer getPictocode() {
286         return pictocode;
287     }
288
289     public Integer getUVIndex() {
290         return UVIndex;
291     }
292
293     public Double getTemperatureMin() {
294         return minTemperature;
295     }
296
297     public Double getTemperatureMax() {
298         return maxTemperature;
299     }
300
301     public Double getTemperatureMean() {
302         return meanTemperature;
303     }
304
305     public Double getFeltTemperatureMin() {
306         return feltTemperatureMin;
307     }
308
309     public Double feltTemperatureMax() {
310         return feltTemperatureMax;
311     }
312
313     public Integer getWindDirection() {
314         return windDirection;
315     }
316
317     public String getCardinalWindDirection() {
318         return cardinalWindDirection;
319     }
320
321     public Integer getPrecipitationProbability() {
322         return precipitationProbability;
323     }
324
325     public String getRainSpot() {
326         return rainSpot;
327     }
328
329     public Image getRainArea() {
330         return rainArea;
331     }
332
333     public Integer getPredictabilityClass() {
334         return predictabilityClass;
335     }
336
337     public Integer getPredictability() {
338         return predictability;
339     }
340
341     public Double getPrecipitation() {
342         return precipitation;
343     }
344
345     public Double getSnowFraction() {
346         return snowFraction;
347     }
348
349     public Integer getSeaLevelPressureMin() {
350         return minSeaLevelPressure;
351     }
352
353     public Integer getSeaLevelPressureMax() {
354         return maxSeaLevelPressure;
355     }
356
357     public Integer getSeaLevelPressureMean() {
358         return meanSeaLevelPressure;
359     }
360
361     public Double getWindSpeedMin() {
362         return minWindSpeed;
363     }
364
365     public Double getWindSpeedMax() {
366         return maxWindSpeed;
367     }
368
369     public Double getWindSpeedMean() {
370         return meanWindSpeed;
371     }
372
373     public Integer getRelativeHumidityMin() {
374         return relativeHumidityMin;
375     }
376
377     public Integer getRelativeHumidityMax() {
378         return relativeHumidityMax;
379     }
380
381     public Integer getRelativeHumidityMean() {
382         return relativeHumidityMean;
383     }
384
385     public Double getConvectivePrecipitation() {
386         return convectivePrecipitation;
387     }
388
389     public Double getPrecipitationHours() {
390         return precipitationHours;
391     }
392
393     public Double getHumidityGreater90Hours() {
394         return humidityGreater90Hours;
395     }
396
397     // derived getters
398     private String getCardinalDirection(int degrees) {
399         /*
400          * 8 directions @ 45 deg. each, centered
401          * N = 337.5-360,0-22.5
402          * NE = 22.5-67.5
403          * E = 67.5-112.5
404          * SE = 112.5-157.5
405          * S = 157.5-202.5
406          * SW = 202.5-247.5
407          * W = 247.5-292.5
408          * NW = 292.5-337.5
409          */
410
411         if (degrees > 337 || degrees < 23) {
412             return "N";
413         }
414
415         if (degrees > 22 && degrees < 68) {
416             return "NE";
417         }
418
419         if (degrees > 67 && degrees < 113) {
420             return "E";
421         }
422
423         if (degrees > 112 && degrees < 158) {
424             return "SE";
425         }
426
427         if (degrees > 157 && degrees < 203) {
428             return "S";
429         }
430
431         if (degrees > 202 && degrees < 248) {
432             return "SW";
433         }
434
435         if (degrees > 247 && degrees < 293) {
436             return "W";
437         }
438
439         return "NW";
440     }
441
442     public Double getSnowFall() {
443         return snowFall;
444     }
445
446     public Image getIcon() {
447         return icon;
448     }
449
450     private String getIconNameForPictocode(int which) {
451         if (which < 1 || which > 17) {
452             return "iday.png";
453         }
454
455         return "iday-" + which + ".png";
456     }
457
458     private Image loadImageIcon(String imageFileName) {
459         BufferedImage buf = null;
460         String configDirectory = OpenHAB.getConfigFolder();
461         File dataFile = new File(new File(configDirectory, "icons/classic/"), imageFileName);
462         if (!dataFile.exists()) {
463             logger.debug("Image file '{}' does not exist. Unable to create imageIcon.", dataFile.getAbsolutePath());
464             return null;
465         }
466
467         try {
468             buf = ImageIO.read(dataFile);
469             logger.trace("Returning image data: {}", buf);
470             return buf;
471         } catch (FileNotFoundException e) {
472             logger.trace("Image file '{}' not found during read attempt", dataFile, e);
473             return null;
474         } catch (IOException e) {
475             logger.debug("Failed to load image file '{}' for weather icon.", dataFile, e);
476             return null;
477         }
478     }
479
480     private Image generateRainAreaImage() {
481         if (rainSpot == null) {
482             logger.debug("No rainspot data exists. Can't generate rain area image.");
483             return null;
484         }
485
486         // @formatter:off
487         /*
488          * Grid position ->           <- String position
489          * [  0  1  2  3  4  5  6 ]   [ 42 43 44 45 46 47 48 ]
490          * [  7  8  9 10 11 12 13 ]   [ 35 36 37 38 39 40 41 ]
491          * [ 14 15 16 17 18 19 20 ]   [ 28 29 30 31 32 33 34 ]
492          * [ 21 22 23 24 25 26 27 ]   [ 21 22 23 24 25 26 27 ]
493          * [ 28 29 30 31 32 33 34 ]   [ 14 15 16 17 18 19 20 ]
494          * [ 35 36 37 38 39 40 41 ]   [  7  8  9 10 11 12 13 ]
495          * [ 42 43 44 45 46 47 48 ]   [  0  1  2  3  4  5  6 ]
496          */
497         // @formatter:on
498
499         String s42 = rainSpot.substring(42);
500         String s35 = rainSpot.substring(35, 42);
501         String s28 = rainSpot.substring(28, 35);
502         String s21 = rainSpot.substring(21, 28);
503         String s14 = rainSpot.substring(14, 21);
504         String s07 = rainSpot.substring(7, 14);
505         String s00 = rainSpot.substring(0, 7);
506         char[] values = new char[49];
507         s00.getChars(0, s00.length(), values, 42);
508         s07.getChars(0, s07.length(), values, 35);
509         s14.getChars(0, s14.length(), values, 28);
510         s21.getChars(0, s21.length(), values, 21);
511         s28.getChars(0, s28.length(), values, 14);
512         s35.getChars(0, s35.length(), values, 7);
513         s42.getChars(0, s42.length(), values, 0);
514         logger.trace("Final grid: {}", values);
515
516         BufferedImage buf = new BufferedImage(350, 350, BufferedImage.TYPE_INT_RGB);
517
518         int gridCell = 0;
519         Color color;
520
521         for (int y = 0; y < 7; y++) {
522             for (int x = 0; x < 7; x++) {
523                 gridCell = y * 7 + x;
524                 color = getColorForIndex(values[gridCell]);
525                 writeGrid(gridCell, buf, color);
526             }
527         }
528
529         return buf;
530     }
531
532     private void writeGrid(int cell, BufferedImage img, Color color) {
533         int r = color.getRed();
534         int g = color.getGreen();
535         int b = color.getBlue();
536         int p = (r << 16) | (g << 8) | b;
537
538         int startX = cell % 7 * 50;
539         int startY = cell / 7 * 50;
540         for (int i = startY; i < startY + 50; i++) {
541             for (int j = startX; j < startX + 50; j++) {
542                 img.setRGB(j, i, p);
543             }
544         }
545     }
546
547     private Color getColorForIndex(char c) {
548         switch (c) {
549             case '0':
550                 return Color.WHITE;
551             case '1':
552                 return Color.GREEN;
553             case '2':
554                 return Color.YELLOW;
555             case '3':
556                 return Color.RED;
557             case '9':
558                 return Color.decode("0x00FF00");
559             default:
560                 return Color.BLACK;
561         }
562     }
563 }