1 package IrrigationController;
4 import iotruntime.slave.IoTAddress;
5 import iotruntime.IoTURL;
7 // Standard Java Packages
8 import java.io.BufferedReader;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.io.InputStreamReader;
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;
27 /** Class WeatherGrabber to get weather data information using the OpenWeatherMap api.
29 * @author Ali Younis <ayounis @ uci.edu>
33 public class WeatherGrabber {
35 /*******************************************************************************************************************************************
39 *******************************************************************************************************************************************/
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";
44 // location in the jar file where the zipcodes csv data is located
45 public static final String ZIPCODE_CSV_FILE = "./resources/zipcode.csv";
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";
53 /*******************************************************************************************************************************************
57 *******************************************************************************************************************************************/
59 // Communications information for interfacing with the api
60 private IoTAddress weatherDataAddress;
62 // Things needed for caching of the data
63 private long timestampOfLastWeatherRetrieval = 0;
64 private int lastZipcodeLookup = 0;
65 private List<DayWeather> weatherDataCache = null;
67 private int savedZipcode = 0;
68 private int savedNumberOfDays = 16;
71 public WeatherGrabber(IoTAddress _IoTAddress) {
72 this.weatherDataAddress = _IoTAddress;
78 /*******************************************************************************************************************************************
82 *******************************************************************************************************************************************/
85 /** Method to get and parse the weather data for the saved zipcode and number of days
87 * @return [List<DayWeather>] list of day by day weather data.
89 public List<DayWeather> getWeatherData() {
90 if (savedZipcode <= 0) {
93 return getWeatherData(savedZipcode, savedNumberOfDays);
97 /** Method to get and parse the weather data for a specific zipcode for a specified number of days
99 * @param _weatherCode [int], zipcode to get the weather for.
100 * @param _numberOfDays [int], number of days to lookup weather for.
102 * @return [List<DayWeather>] list of day by day weather data.
104 public List<DayWeather> getWeatherData(int _zipcode, int _numberOfDays) {
106 // less than or equal to 0 means that the list will be empty
107 if (_numberOfDays <= 0) {
108 return new ArrayList<DayWeather>();
111 // get current date and time
112 Date date = new Date();
114 // check if we ever got the weather data
115 if (this.timestampOfLastWeatherRetrieval != 0) {
117 // check the elapsed time since we got the weather data
118 long timeElapsedFromLastWeatherDataRead = date.getTime() - this.timestampOfLastWeatherRetrieval;
119 timeElapsedFromLastWeatherDataRead /= 1000; // convert to seconds
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) {
126 // make sure the cached weather data is for the zipcode that we are being asked for
127 if (lastZipcodeLookup == _zipcode) {
129 // now check that we actually have weather data available
130 if (weatherDataCache != null) {
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;
142 // convert zipcode into weather api specific area code
143 int weatherLocationCode = getWeatherCode(_zipcode);
145 // check if weather information can be attained for the zipcode specified
146 if (weatherLocationCode == -1) {
150 // save information for caching
151 lastZipcodeLookup = _zipcode;
152 timestampOfLastWeatherRetrieval = date.getTime();
154 // try to get the weather data XML from the server
155 InputStream inputStream = getXmlData(weatherLocationCode, _numberOfDays);
156 if (inputStream == null) {
160 // convert the XML into an easier to use format
161 weatherDataCache = parseXmlData(inputStream);
162 return weatherDataCache;
166 /** Method to set the zipcode of this weather grabber
168 * @param _zipcode [int], zipcode to get the weather for.
170 * @return [void] None.
172 public void setZipcode(int _zipcode) {
173 savedZipcode = _zipcode;
177 /** Method to set the number of days of this weather grabber
179 * @param _numberOfDays [int], number of days to get the weather for.
181 * @return [void] None.
183 public void setNumberOfDays(int _numberOfDays) {
184 savedNumberOfDays = _numberOfDays;
188 /** Method to get the zipcode of this weather grabber
190 * @return [int] zipcode of the weather grabber.
192 public int getZipcode() {
197 /** Method to get the number of days of this weather grabber
199 * @return [int] number of days of the weather grabber.
201 public int getNumberOfDays() {
202 return savedNumberOfDays;
205 /*******************************************************************************************************************************************
209 *******************************************************************************************************************************************/
211 /** Method to get the XML file that the weather api returns after a query
213 * @param _weatherCode [int], weather api specific location code.
214 * @param _numberOfDays [int], number of days to lookup weather for.
216 * @return [InputStream] InputStream containing the xml file data.
218 private InputStream getXmlData(int _weatherCode, int _numberOfDays) {
220 // We can only get a max of 16 days into the future
221 if (_numberOfDays > 16) {
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";
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);
233 // sets the connection ending address
234 urlConnection.setURL(urlEnd);
235 System.out.println("Connected to URL!");
238 return urlConnection.openStream();
239 } catch (Exception e) {
243 // something happened and the URL connection could not be opened.
248 /** Method to parse the XML file that the weather api returns after a query
250 * @param inputStream [InputStream], input stream containing weather XML file.
252 * @return [List<DayWeather>] list of day by day weather data.
254 private List<DayWeather> parseXmlData(InputStream inputStream) {
257 // array to store the parsed weather data per day
258 ArrayList<DayWeather> weatherDataList = new ArrayList<DayWeather>();
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);
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) {
271 // the node for each of the individual days
272 Node dayNode = forcastNodes.item(i);
274 // make sure the node is an actually day node and not an invalid node
275 if (dayNode.getNodeType() == Node.ELEMENT_NODE) {
277 // Convert to elements since we can only pull data out of elements not nodes
278 Element dayElement = (Element)dayNode;
280 // Information is saved in child nodes
281 NodeList informationNodes = dayNode.getChildNodes();
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 = "";
299 // go through the child info nodes and pull the data out
300 for (int j = 0; j < informationNodes.getLength(); ++j) {
302 Node informationNode = informationNodes.item(j);
304 if (informationNode.getNodeType() == Node.ELEMENT_NODE) {
306 Element informationElement = (Element)informationNode;
307 String informationName = informationElement.getTagName();
309 if (informationName.equals("symbol")) {
310 weatherInfoCode = informationElement.getAttribute("number");
312 } else if (informationName.equals("windDirection")) {
313 windDirectionText = informationElement.getAttribute("code");
314 windDirectionDegrees = informationElement.getAttribute("deg");
316 } else if (informationName.equals("windSpeed")) {
317 windSpeed = informationElement.getAttribute("mps");
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");
327 } else if (informationName.equals("pressure")) {
328 pressure = informationElement.getAttribute("value");
330 } else if (informationName.equals("humidity")) {
331 humidity = informationElement.getAttribute("value");
333 } else if (informationName.equals("clouds")) {
334 cloudCoverage = informationElement.getAttribute("all");
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,
345 windDirectionDegrees,
357 // add this day to the list
358 weatherDataList.add(dayWeather);
362 // return the weather data to the caller
363 return weatherDataList;
364 } catch (Exception e) {
365 System.err.println("unable to load XML: ");
369 // There was an error parsing so return null
374 /** Method to weather api specific location code for a given zipcode.
376 * @param _zipcode [double], zipcode to lookup.
378 * @return [double] weather api location code for the given zipcode.
380 private int getWeatherCode(int _zipcode) {
382 // used for reading the .csv files
383 BufferedReader bufferedReader = null;
385 // latitude and longitude of the zipcode, will stay -1 if zipcode not found
386 float locationLatitude = -1;
387 float locationLongitude = -1;
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));
397 if (bufferedReader != null)
398 System.out.println("DEBUG: Zip code file read successfully!");
400 // read the .csv file line by line and parse the file
402 while ((line = bufferedReader.readLine()) != null) {
404 // split lines on commas, should result in an array of strings of size 3
405 String[] splitString = line.split(",");
407 // make sure the line has the correct format
408 if (splitString.length != 3) {
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]);
417 // if the zipcode of this line matches the zipcode that was requested
418 if (zipcodeValue == _zipcode) {
420 // It does get the lat and long
421 locationLatitude = latValue;
422 locationLongitude = lonValue;
424 // dont need to search anymore since we found the exact zipcode
429 } catch (Exception e) {
434 // always close the file since we are going to use the buffered reader again
435 if (bufferedReader != null) {
437 bufferedReader.close();
438 } catch (IOException e) {
444 // if zipcode was not found then will be -1 and we should report an error
445 if (locationLatitude == -1) {
449 // reset so we can use it again
450 bufferedReader = null;
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;
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));
463 if (bufferedReader != null)
464 System.out.println("DEBUG: Area code file read successfully!");
466 // read the .csv file line by line and parse the file
468 while ((line = bufferedReader.readLine()) != null) {
470 // split lines on commas, should result in an array of strings of size 3
471 String[] splitString = line.split(",");
473 // make sure the line has the correct format
474 if (splitString.length != 3) {
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]);
484 // calculate the distance from this lat long from the one matched to the zipcode
485 float currDistance = (float)distance(latValue, lonValue, locationLatitude, locationLongitude);
487 // of distance is closer
488 if (currDistance <= closestDistance) {
491 closestDistance = currDistance;
492 closestWeatherCode = weatheCodeValue;
495 } catch (Exception e) {
500 // make sure we close the file
501 if (bufferedReader != null) {
503 bufferedReader.close();
504 } catch (IOException e) {
510 return closestWeatherCode;
514 /** Static Method to get distance from 2 latitude and longitude positions.
515 * Adapted from https://www.geodatasource.com/developers/java
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.
522 * @return [double] distance in miles.
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;
534 /** Static Method convert degrees to radians.
535 * Adapted from https://www.geodatasource.com/developers/java
537 * @param _degree [double], degrees.
539 * @return [double] radians value.
541 private static double deg2rad(double _degree) {
542 return _degree * 3.141592653589793 / 180.0;
546 /** Static Method convert radians to degrees.
547 * Adapted from https://www.geodatasource.com/developers/java
549 * @param _rad [double], radians.
551 * @return [double] degrees value.
553 private static double rad2deg(double _rad) {
554 return _rad * 180.0 / 3.141592653589793;
559 /*******************************************************************************************************************************************
560 ** Main Method used for testing
561 *******************************************************************************************************************************************/
562 /* public static void main(String[] arrstring) {
563 System.out.println("WE ARE RUNNING!");
567 IoTAddress devAddress = new IoTAddress("api.openweathermap.org");
568 WeatherGrabber we = new WeatherGrabber(devAddress);
570 //List<DayWeather> dw = we.getWeatherData(92130, 16);
571 List<DayWeather> dw = we.getWeatherData(92612, 255);
574 for (DayWeather day : dw) {
575 System.out.println(day);
578 } catch (Exception e) {