c47170bd6cffd27c626cd73f6a63fbfceb50fb67
[iot2.git] / benchmarks / IrrigationController / WeatherGrabber.java
1 package IrrigationController;
2
3 // IoT Packages
4 import iotruntime.slave.IoTAddress;
5 import iotruntime.IoTURL;
6
7 // Standard Java Packages
8 import java.io.BufferedReader;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.io.InputStreamReader;
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.PrintStream;
15 import java.io.Reader;
16 import java.util.ArrayList;
17 import java.util.Date;
18 import java.util.List;
19 import javax.xml.parsers.DocumentBuilder;
20 import javax.xml.parsers.DocumentBuilderFactory;
21 import org.w3c.dom.Document;
22 import org.w3c.dom.Element;
23 import org.w3c.dom.Node;
24 import org.w3c.dom.NodeList;
25
26
27 /** Class WeatherGrabber to get weather data information using the OpenWeatherMap api.
28  *
29  * @author      Ali Younis <ayounis @ uci.edu>
30  * @version     1.0
31  * @since       2016-03-24
32  */
33 public class WeatherGrabber {
34
35         /*******************************************************************************************************************************************
36         **
37         **  Constants
38         **
39         *******************************************************************************************************************************************/
40
41         // KEy for using the api, needed for authentication with the api when making calls to it
42         public static final String API_KEY_ID = "fbc55f11190c6472e14b7743f8a38c92";
43
44         // location in the jar file where the zipcodes csv data is located
45         public static final String ZIPCODE_CSV_FILE = "./resources/zipcode.csv";
46
47         // location in the jar file where the area codes csv data is located
48         public static final String AREA_CODE_CSV_FILE = "./resources/area_codes.csv";
49
50
51
52
53         /*******************************************************************************************************************************************
54         **
55         **  Variables
56         **
57         *******************************************************************************************************************************************/
58
59         // Communications information for interfacing with the api
60         private IoTAddress weatherDataAddress;
61
62         // Things needed for caching of the data
63         private long timestampOfLastWeatherRetrieval = 0;
64         private int lastZipcodeLookup = 0;
65         private List<DayWeather> weatherDataCache = null;
66
67         private int savedZipcode = 0;
68         private int savedNumberOfDays = 16;
69
70
71         public WeatherGrabber(IoTAddress _IoTAddress) {
72                 this.weatherDataAddress = _IoTAddress;
73         }
74
75
76
77
78         /*******************************************************************************************************************************************
79         **
80         **  Public Methods
81         **
82         *******************************************************************************************************************************************/
83
84
85         /** Method to get and parse the weather data for the saved zipcode and number of days
86          *
87          *   @return [List<DayWeather>] list of day by day weather data.
88          */
89         public List<DayWeather> getWeatherData() {
90                 if (savedZipcode <= 0) {
91                         return null;
92                 }
93                 return getWeatherData(savedZipcode, savedNumberOfDays);
94         }
95
96
97         /** Method to get and parse the weather data for a specific zipcode for a specified number of days
98          *
99          *   @param _weatherCode  [int], zipcode to get the weather for.
100          *   @param _numberOfDays [int], number of days to lookup weather for.
101          *
102          *   @return [List<DayWeather>] list of day by day weather data.
103          */
104         public List<DayWeather> getWeatherData(int _zipcode, int _numberOfDays) {
105
106                 // less than or equal to 0 means that the list will be empty
107                 if (_numberOfDays <= 0) {
108                         return new ArrayList<DayWeather>();
109                 }
110
111                 // get current date and time
112                 Date date = new Date();
113
114                 // check if we ever got the weather data
115                 if (this.timestampOfLastWeatherRetrieval != 0) {
116
117                         // check the elapsed time since we got the weather data
118                         long timeElapsedFromLastWeatherDataRead = date.getTime() - this.timestampOfLastWeatherRetrieval;
119                         timeElapsedFromLastWeatherDataRead /= 1000;                             // convert to seconds
120
121                         // we got the cached weather data less than 12 hours ago so just use the cached data
122                         // The api limits how many calls we can make in a given time and so we should cache the data
123                         // and reuse it.  Also the weather doesnt change that fast
124                         if (timestampOfLastWeatherRetrieval <= 43200) {
125
126                                 // make sure the cached weather data is for the zipcode that we are being asked for
127                                 if (lastZipcodeLookup == _zipcode) {
128
129                                         // now check that we actually have weather data available
130                                         if (weatherDataCache != null) {
131
132                                                 // make sure we have enough weather data, we may only have data for some of the days that
133                                                 // are being requested but not all
134                                                 if (weatherDataCache.size() >= _numberOfDays) {
135                                                         return weatherDataCache;
136                                                 }
137                                         }
138                                 }
139                         }
140                 }
141
142                 // convert zipcode into weather api specific area code
143                 int weatherLocationCode = getWeatherCode(_zipcode);
144
145                 // check if weather information can be attained for the zipcode specified
146                 if (weatherLocationCode == -1) {
147                         return null;
148                 }
149
150                 // save information for caching
151                 lastZipcodeLookup = _zipcode;
152                 timestampOfLastWeatherRetrieval = date.getTime();
153
154                 // try to get the weather data XML from the server
155                 InputStream inputStream = getXmlData(weatherLocationCode, _numberOfDays);
156                 if (inputStream == null) {
157                         return null;
158                 }
159
160                 // convert the XML into an easier to use format
161                 weatherDataCache = parseXmlData(inputStream);
162                 return weatherDataCache;
163         }
164
165
166         /** Method to set the zipcode of this weather grabber
167          *
168          *   @param _zipcode  [int], zipcode to get the weather for.
169          *
170          *   @return [void] None.
171          */
172         public void setZipcode(int _zipcode) {
173                 savedZipcode = _zipcode;
174         }
175
176
177         /** Method to set the number of days of this weather grabber
178          *
179          *   @param _numberOfDays  [int], number of days to get the weather for.
180          *
181          *   @return [void] None.
182          */
183         public void setNumberOfDays(int _numberOfDays) {
184                 savedNumberOfDays = _numberOfDays;
185         }
186
187
188         /** Method to get the zipcode of this weather grabber
189          *
190          *   @return [int] zipcode of the weather grabber.
191          */
192         public int getZipcode() {
193                 return savedZipcode;
194         }
195
196
197         /** Method to get the number of days of this weather grabber
198          *
199          *   @return [int] number of days of the weather grabber.
200          */
201         public int getNumberOfDays() {
202                 return savedNumberOfDays;
203         }
204
205         /*******************************************************************************************************************************************
206         **
207         **  Helper Methods
208         **
209         *******************************************************************************************************************************************/
210
211         /** Method to get the XML file that the weather api returns after a query
212          *
213          *   @param _weatherCode  [int], weather api specific location code.
214          *   @param _numberOfDays [int], number of days to lookup weather for.
215          *
216          *   @return [InputStream] InputStream containing the xml file data.
217          */
218         private InputStream getXmlData(int _weatherCode, int _numberOfDays) {
219
220                 // We can only get a max of 16 days into the future
221                 if (_numberOfDays > 16) {
222                         _numberOfDays = 16;
223                 }
224
225                 // Create the url ending path with all the parameters needed to get the XML file
226                 String urlEnd = "/data/2.5/forecast/daily?id=" + Integer.toString(_weatherCode) + "&units=imperial&mode=xml&cnt=" + Integer.toString(_numberOfDays) + "&APPID=" + "fbc55f11190c6472e14b7743f8a38c92";
227
228                 // Communication object created based on address passed in by the runtime system
229                 IoTURL urlConnection = new IoTURL(weatherDataAddress);
230                 System.out.println("URL: " + urlEnd);
231
232                 try {
233                         // sets the connection ending address
234                         urlConnection.setURL(urlEnd);
235                         System.out.println("Connected to URL!");
236
237                         // Return the stream
238                         return urlConnection.openStream();
239                 } catch (Exception e) {
240                         e.printStackTrace();
241                 }
242
243                 // something happened and the URL connection could not be opened.
244                 return null;
245         }
246
247
248         /** Method to parse the XML file that the weather api returns after a query
249          *
250          *   @param inputStream [InputStream], input stream containing weather XML file.
251          *
252          *   @return [List<DayWeather>] list of day by day weather data.
253          */
254         private List<DayWeather> parseXmlData(InputStream inputStream) {
255                 try {
256
257                         // array to store the parsed weather data per day
258                         ArrayList<DayWeather> weatherDataList = new ArrayList<DayWeather>();
259
260                         // stuff needed to open and parse an XML file
261                         DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
262                         documentBuilderFactory.setValidating(false);
263                         documentBuilderFactory.setNamespaceAware(false);
264                         DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
265                         Document document = documentBuilder.parse(inputStream);
266
267                         // get the forecast node that is the main node for each of the day subnodes
268                         NodeList forcastNodes = document.getElementsByTagName("forecast").item(0).getChildNodes();
269                         for (int i = 0; i < forcastNodes.getLength(); ++i) {
270
271                                 // the node for each of the individual days
272                                 Node dayNode = forcastNodes.item(i);
273
274                                 // make sure the node is an actually day node and not an invalid node
275                                 if (dayNode.getNodeType() == Node.ELEMENT_NODE) {
276
277                                         // Convert to elements since we can only pull data out of elements not nodes
278                                         Element dayElement = (Element)dayNode;
279
280                                         // Information is saved in child nodes
281                                         NodeList informationNodes = dayNode.getChildNodes();
282
283                                         // Data that pulls the pre-parse data from the XML file
284                                         String dateString = dayElement.getAttribute("day");
285                                         String weatherInfoCode = "";
286                                         String windDirectionText = "";
287                                         String windDirectionDegrees = "";
288                                         String windSpeed = "";
289                                         String temperatureDay = "";
290                                         String temperatureEvening = "";
291                                         String temperatureMorning = "";
292                                         String temperatureNight = "";
293                                         String temperatureMax = "";
294                                         String temperatureMin = "";
295                                         String pressure = "";
296                                         String humidity = "";
297                                         String cloudCoverage = "";
298
299                                         // go through the child info nodes and pull the data out
300                                         for (int j = 0; j < informationNodes.getLength(); ++j) {
301
302                                                 Node informationNode = informationNodes.item(j);
303
304                                                 if (informationNode.getNodeType() == Node.ELEMENT_NODE) {
305
306                                                         Element informationElement = (Element)informationNode;
307                                                         String informationName = informationElement.getTagName();
308
309                                                         if (informationName.equals("symbol")) {
310                                                                 weatherInfoCode = informationElement.getAttribute("number");
311
312                                                         } else if (informationName.equals("windDirection")) {
313                                                                 windDirectionText = informationElement.getAttribute("code");
314                                                                 windDirectionDegrees = informationElement.getAttribute("deg");
315
316                                                         } else if (informationName.equals("windSpeed")) {
317                                                                 windSpeed = informationElement.getAttribute("mps");
318
319                                                         } else if (informationName.equals("temperature")) {
320                                                                 temperatureDay = informationElement.getAttribute("day");
321                                                                 temperatureEvening = informationElement.getAttribute("eve");
322                                                                 temperatureMorning = informationElement.getAttribute("morn");
323                                                                 temperatureNight = informationElement.getAttribute("night");
324                                                                 temperatureMax = informationElement.getAttribute("max");
325                                                                 temperatureMin = informationElement.getAttribute("min");
326
327                                                         } else if (informationName.equals("pressure")) {
328                                                                 pressure = informationElement.getAttribute("value");
329
330                                                         } else if (informationName.equals("humidity")) {
331                                                                 humidity = informationElement.getAttribute("value");
332
333                                                         } else if (informationName.equals("clouds")) {
334                                                                 cloudCoverage = informationElement.getAttribute("all");
335
336                                                         }
337                                                 }
338                                         }
339
340                                         // Create the day object,  this object will automatically convert the string data into
341                                         // the appropriate data types
342                                         DayWeather dayWeather = new DayWeather(dateString,
343                                                                                                                                                                                                  weatherInfoCode,
344                                                                                                                                                                                                  windDirectionText,
345                                                                                                                                                                                                  windDirectionDegrees,
346                                                                                                                                                                                                  windSpeed,
347                                                                                                                                                                                                  temperatureDay,
348                                                                                                                                                                                                  temperatureEvening,
349                                                                                                                                                                                                  temperatureMorning,
350                                                                                                                                                                                                  temperatureNight,
351                                                                                                                                                                                                  temperatureMax,
352                                                                                                                                                                                                  temperatureMin,
353                                                                                                                                                                                                  pressure,
354                                                                                                                                                                                                  humidity,
355                                                                                                                                                                                                  cloudCoverage);
356
357                                         // add this day to the list
358                                         weatherDataList.add(dayWeather);
359                                 }
360                         }
361
362                         // return the weather data to the caller
363                         return weatherDataList;
364                 } catch (Exception e) {
365                         System.err.println("unable to load XML: ");
366                         e.printStackTrace();
367                 }
368
369                 // There was an error parsing so return null
370                 return null;
371         }
372
373
374         /** Method to weather api specific location code for a given zipcode.
375          *
376          *   @param _zipcode [double], zipcode to lookup.
377          *
378          *   @return [double] weather api location code for the given zipcode.
379          */
380         private int getWeatherCode(int _zipcode) {
381
382                 // used for reading the .csv files
383                 BufferedReader bufferedReader = null;
384
385                 // latitude and longitude of the zipcode, will stay -1 if zipcode not found
386                 float locationLatitude = -1;
387                 float locationLongitude = -1;
388
389
390                 try {
391
392                         // Open the csv file
393                         //InputStream inputStream = this.getClass().getResourceAsStream(ZIPCODE_CSV_FILE);
394                         InputStream inputStream = new FileInputStream(new File(ZIPCODE_CSV_FILE));
395                         bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
396
397                         if (bufferedReader != null)
398                                 System.out.println("DEBUG: Zip code file read successfully!");
399
400                         // read the .csv file line by line and parse the file
401                         String line = "";
402                         while ((line = bufferedReader.readLine()) != null) {
403
404                                 // split lines on commas, should result in an array of strings of size 3
405                                 String[] splitString = line.split(",");
406
407                                 // make sure the line has the correct format
408                                 if (splitString.length != 3) {
409                                         continue;
410                                 }
411
412                                 // parse the line for the individual data pieces
413                                 int zipcodeValue = Integer.parseInt(splitString[0]);
414                                 float latValue = Float.parseFloat(splitString[1]);
415                                 float lonValue = Float.parseFloat(splitString[2]);
416
417                                 // if the zipcode of this line matches the zipcode that was requested
418                                 if (zipcodeValue == _zipcode) {
419
420                                         // It does get the lat and long
421                                         locationLatitude = latValue;
422                                         locationLongitude = lonValue;
423
424                                         // dont need to search anymore since we found the exact zipcode
425                                         break;
426                                 }
427                         }
428
429                 } catch (Exception e) {
430                         e.printStackTrace();
431                 }
432                 finally {
433
434                         // always close the file since we are going to use the buffered reader again
435                         if (bufferedReader != null) {
436                                 try {
437                                         bufferedReader.close();
438                                 } catch (IOException e) {
439                                         e.printStackTrace();
440                                 }
441                         }
442                 }
443
444                 // if zipcode was not found then will be -1 and we should report an error
445                 if (locationLatitude == -1) {
446                         return -1;
447                 }
448
449                 // reset so we can use it again
450                 bufferedReader = null;
451
452                 // used to store the closest location matched since lat and long may not match up exactly
453                 float closestDistance = 100000;
454                 int closestWeatherCode = 0;
455
456
457                 try {
458                         // Open the csv file
459                         //InputStream inputStream = this.getClass().getResourceAsStream(AREA_CODE_CSV_FILE);
460                         InputStream inputStream = new FileInputStream(new File(AREA_CODE_CSV_FILE));
461                         bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
462
463                         if (bufferedReader != null)
464                                 System.out.println("DEBUG: Area code file read successfully!");
465
466                         // read the .csv file line by line and parse the file
467                         String line = "";
468                         while ((line = bufferedReader.readLine()) != null) {
469
470                                 // split lines on commas, should result in an array of strings of size 3
471                                 String[] splitString = line.split(",");
472
473                                 // make sure the line has the correct format
474                                 if (splitString.length != 3) {
475                                         continue;
476                                 }
477
478                                 // parse the line for the individual data pieces
479                                 int weatheCodeValue = Integer.parseInt(splitString[0]);
480                                 float lonValue = Float.parseFloat(splitString[1]);
481                                 float latValue = Float.parseFloat(splitString[2]);
482
483
484                                 // calculate the distance from this lat long from the one matched to the zipcode
485                                 float currDistance = (float)distance(latValue, lonValue, locationLatitude, locationLongitude);
486
487                                 // of distance is closer
488                                 if (currDistance <= closestDistance) {
489
490                                         // save this entry
491                                         closestDistance = currDistance;
492                                         closestWeatherCode = weatheCodeValue;
493                                 }
494                         }
495                 } catch (Exception e) {
496                         e.printStackTrace();
497                 }
498                 finally {
499
500                         // make sure we close the file
501                         if (bufferedReader != null) {
502                                 try {
503                                         bufferedReader.close();
504                                 } catch (IOException e) {
505                                         e.printStackTrace();
506                                 }
507                         }
508                 }
509
510                 return closestWeatherCode;
511         }
512
513
514         /** Static Method to get distance from 2 latitude and longitude positions.
515          *   Adapted from https://www.geodatasource.com/developers/java
516          *
517          *   @param _lat1   [double], latitude pair 1.
518          *   @param _long1  [double], longitude pair 1.
519          *   @param _lat2   [double], latitude pair 2.
520          *   @param _long2  [double], longitude pair 2.
521          *
522          *   @return [double] distance in miles.
523          */
524         private static double distance(double _lat1, double _lon1, double _lat2, double _lon2) {
525                 double theta = _lon1 - _lon2;
526                 double dist = Math.sin(deg2rad(_lat1)) * Math.sin(deg2rad(_lat2)) + Math.cos(deg2rad(_lat1)) * Math.cos(deg2rad(_lat2)) * Math.cos(deg2rad(theta));
527                 dist = Math.acos(dist);
528                 dist = rad2deg(dist);
529                 dist = dist * 60 * 1.1515;
530                 return (dist);
531         }
532
533
534         /** Static Method convert degrees to radians.
535          *   Adapted from https://www.geodatasource.com/developers/java
536          *
537          *   @param _degree  [double], degrees.
538          *
539          *   @return [double] radians value.
540          */
541         private static double deg2rad(double _degree) {
542                 return _degree * 3.141592653589793 / 180.0;
543         }
544
545
546         /** Static Method convert radians to degrees.
547          *   Adapted from https://www.geodatasource.com/developers/java
548          *
549          *   @param _rad  [double], radians.
550          *
551          *   @return [double] degrees value.
552          */
553         private static double rad2deg(double _rad) {
554                 return _rad * 180.0 / 3.141592653589793;
555         }
556
557
558
559         /*******************************************************************************************************************************************
560         **  Main Method used for testing
561         *******************************************************************************************************************************************/
562 /*      public static void main(String[] arrstring) {
563                 System.out.println("WE ARE RUNNING!");
564
565
566                 try {
567                         IoTAddress devAddress = new IoTAddress("api.openweathermap.org");
568                         WeatherGrabber we = new WeatherGrabber(devAddress);
569
570                         //List<DayWeather> dw = we.getWeatherData(92130, 16);
571                         List<DayWeather> dw = we.getWeatherData(92612, 255);
572
573
574                         for (DayWeather day : dw) {
575                                 System.out.println(day);
576                         }
577
578                 } catch (Exception e) {
579
580                 }
581         }*/
582 }
583
584