2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.meteoblue.internal;
15 import java.awt.Color;
16 import java.awt.Image;
17 import java.awt.image.BufferedImage;
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;
27 import javax.imageio.ImageIO;
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;
37 * Model of forecast data.
39 * @author Chris Carman - Initial contribution
41 public class Forecast {
42 private final Logger logger = LoggerFactory.getLogger(Forecast.class);
45 private Double latitude;
46 private Double longitude;
47 private Integer height;
48 private String timeZoneAbbreviation;
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;
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;
91 private String cardinalWindDirection;
92 private String iconName;
94 private Image rainArea;
95 private Double snowFall;
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.");
102 if (dataDay == null) {
103 throw new IllegalArgumentException("Received no data_day information.");
105 if (whichDay > dataDay.getTime().length - 1) {
106 throw new IndexOutOfBoundsException("No data received for day " + whichDay);
109 // extract the metadata fields
110 latitude = metadata.getLatitude();
111 longitude = metadata.getLongitude();
112 height = metadata.getHeight();
113 timeZoneAbbreviation = metadata.getTimeZoneAbbreviation();
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);
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();
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);
143 // extract the data_day fields
144 String timeString = dataDay.getTime()[whichDay];
146 Calendar c = Calendar.getInstance(TimeZone.getTimeZone(timeZoneAbbreviation));
147 SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
148 c.setTime(formatter.parse(timeString));
150 } catch (ParseException e) {
151 logger.debug("Failed to parse the value '{}' as a date.", timeString);
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];
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);
220 public Object getDatapoint(String datapointName) {
221 if ("condition".equals(datapointName)) {
222 return String.valueOf(pictocode);
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);
232 } catch (Exception e) {
233 logger.warn("An unexpected error occurred while trying to access the datapoint '{}'", datapointName, e);
239 public Integer getHeight() {
244 public String getTimeUnits() {
248 public String getPredictabilityUnits() {
249 return predictabilityUnits;
252 public String getPrecipitationProbabilityUnits() {
253 return precipitationProbabilityUnits;
256 public String getPressureUnits() {
257 return pressureUnits;
260 public String getRelativeHumidityUnits() {
261 return relativeHumidityUnits;
264 public String getTemperatureUnits() {
265 return temperatureUnits;
268 public String getWindDirectionUnits() {
269 return windDirectionUnits;
272 public String getPrecipitationUnits() {
273 return precipitationUnits;
276 public String getWindSpeedUnits() {
277 return windSpeedUnits;
281 public Calendar getForecastDate() {
285 public Integer getPictocode() {
289 public Integer getUVIndex() {
293 public Double getTemperatureMin() {
294 return minTemperature;
297 public Double getTemperatureMax() {
298 return maxTemperature;
301 public Double getTemperatureMean() {
302 return meanTemperature;
305 public Double getFeltTemperatureMin() {
306 return feltTemperatureMin;
309 public Double feltTemperatureMax() {
310 return feltTemperatureMax;
313 public Integer getWindDirection() {
314 return windDirection;
317 public String getCardinalWindDirection() {
318 return cardinalWindDirection;
321 public Integer getPrecipitationProbability() {
322 return precipitationProbability;
325 public String getRainSpot() {
329 public Image getRainArea() {
333 public Integer getPredictabilityClass() {
334 return predictabilityClass;
337 public Integer getPredictability() {
338 return predictability;
341 public Double getPrecipitation() {
342 return precipitation;
345 public Double getSnowFraction() {
349 public Integer getSeaLevelPressureMin() {
350 return minSeaLevelPressure;
353 public Integer getSeaLevelPressureMax() {
354 return maxSeaLevelPressure;
357 public Integer getSeaLevelPressureMean() {
358 return meanSeaLevelPressure;
361 public Double getWindSpeedMin() {
365 public Double getWindSpeedMax() {
369 public Double getWindSpeedMean() {
370 return meanWindSpeed;
373 public Integer getRelativeHumidityMin() {
374 return relativeHumidityMin;
377 public Integer getRelativeHumidityMax() {
378 return relativeHumidityMax;
381 public Integer getRelativeHumidityMean() {
382 return relativeHumidityMean;
385 public Double getConvectivePrecipitation() {
386 return convectivePrecipitation;
389 public Double getPrecipitationHours() {
390 return precipitationHours;
393 public Double getHumidityGreater90Hours() {
394 return humidityGreater90Hours;
398 private String getCardinalDirection(int degrees) {
400 * 8 directions @ 45 deg. each, centered
401 * N = 337.5-360,0-22.5
411 if (degrees > 337 || degrees < 23) {
415 if (degrees > 22 && degrees < 68) {
419 if (degrees > 67 && degrees < 113) {
423 if (degrees > 112 && degrees < 158) {
427 if (degrees > 157 && degrees < 203) {
431 if (degrees > 202 && degrees < 248) {
435 if (degrees > 247 && degrees < 293) {
442 public Double getSnowFall() {
446 public Image getIcon() {
450 private String getIconNameForPictocode(int which) {
451 if (which < 1 || which > 17) {
455 return "iday-" + which + ".png";
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());
468 buf = ImageIO.read(dataFile);
469 logger.trace("Returning image data: {}", buf);
471 } catch (FileNotFoundException e) {
472 logger.trace("Image file '{}' not found during read attempt", dataFile, e);
474 } catch (IOException e) {
475 logger.debug("Failed to load image file '{}' for weather icon.", dataFile, e);
480 private Image generateRainAreaImage() {
481 if (rainSpot == null) {
482 logger.debug("No rainspot data exists. Can't generate rain area image.");
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 ]
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);
516 BufferedImage buf = new BufferedImage(350, 350, BufferedImage.TYPE_INT_RGB);
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);
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;
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++) {
547 private Color getColorForIndex(char c) {
558 return Color.decode("0x00FF00");