package com.github.dvdme.ForecastIOLib; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; public class ForecastIO { private static final String ForecastIOURL = "https://api.darksky.net/forecast/"; private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); private String ForecastIOApiKey = ""; private String unitsURL; private String timeURL; private String excludeURL; private String langURL; private boolean extend; private int connectTimeout = 30000; private int readTimeout = 30000; private String Cache_Control; private String Expires; private String X_Forecast_API_Calls; private String X_Response_Time; private String rawResponse; private Proxy proxy_to_use; public static final String UNITS_US = "us"; public static final String UNITS_SI = "si"; public static final String UNITS_CA = "ca"; public static final String UNITS_UK = "uk"; public static final String UNITS_AUTO = "auto"; public static final String LANG_BOSNIAN = "bs"; public static final String LANG_GERMAN = "de"; public static final String LANG_ENGLISH = "en"; public static final String LANG_SPANISH = "es"; public static final String LANG_FRENCH = "fr"; public static final String LANG_ITALIAN = "it"; public static final String LANG_DUTCH = "nl"; public static final String LANG_POLISH = "pl"; public static final String LANG_PORTUGUESE = "pt"; public static final String LANG_TETUM = "tet"; public static final String LANG_PIG_LATIN = "x-pig-latin"; public static final String LANG_RUSSIAN = "ru"; private JsonObject forecast; private JsonObject currently; private JsonObject minutely; private JsonObject hourly; private JsonObject daily; private JsonObject flags; private JsonArray alerts; public ForecastIO(String API_KEY){ if (API_KEY.length()==32) { this.ForecastIOApiKey = API_KEY; this.forecast = new JsonObject(); this.currently = new JsonObject(); this.minutely = new JsonObject(); this.hourly = new JsonObject(); this.daily = new JsonObject(); this.flags = new JsonObject(); this.alerts = new JsonArray(); this.timeURL = null; this.excludeURL = null; this.extend = false; this.unitsURL = UNITS_AUTO; this.langURL = LANG_ENGLISH; this.proxy_to_use = null; } else { System.err.println("The API Key doesn't seam to be valid."); } }//construtor - end public ForecastIO(String LATITUDE, String LONGITUDE, String API_KEY){ if (API_KEY.length()==32) { this.ForecastIOApiKey = API_KEY; this.forecast = new JsonObject(); this.currently = new JsonObject(); this.minutely = new JsonObject(); this.hourly = new JsonObject(); this.daily = new JsonObject(); this.flags = new JsonObject(); this.alerts = new JsonArray(); this.timeURL = null; this.excludeURL = null; this.extend = false; this.unitsURL = UNITS_AUTO; this.langURL = LANG_ENGLISH; this.proxy_to_use = null; getForecast(LATITUDE, LONGITUDE); } else { System.err.println("The API Key doesn't seam to be valid."); } }//construtor - end public ForecastIO(String LATITUDE, String LONGITUDE, String PROXYNAME, int PROXYPORT, String API_KEY){ if (API_KEY.length()==32) { this.ForecastIOApiKey = API_KEY; this.forecast = new JsonObject(); this.currently = new JsonObject(); this.minutely = new JsonObject(); this.hourly = new JsonObject(); this.daily = new JsonObject(); this.flags = new JsonObject(); this.alerts = new JsonArray(); this.timeURL = null; this.excludeURL = null; this.extend = false; this.unitsURL = UNITS_AUTO; this.langURL = LANG_ENGLISH; this.proxy_to_use = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXYNAME, PROXYPORT)); getForecast(LATITUDE, LONGITUDE); } else { System.err.println("The API Key doesn't seam to be valid."); } }//construtor - end public ForecastIO(String LATITUDE, String LONGITUDE, String UNITS, String LANG, String API_KEY){ if (API_KEY.length()==32) { this.ForecastIOApiKey = API_KEY; this.forecast = new JsonObject(); this.currently = new JsonObject(); this.minutely = new JsonObject(); this.hourly = new JsonObject(); this.daily = new JsonObject(); this.flags = new JsonObject(); this.alerts = new JsonArray(); this.timeURL = null; this.excludeURL = null; this.extend = false; this.proxy_to_use = null; this.setUnits(UNITS); this.setLang(LANG); getForecast(LATITUDE, LONGITUDE); } else { System.err.println("The API Key doesn't seam to be valid."); } }//construtor - end public ForecastIO(String LATITUDE, String LONGITUDE, String UNITS, String LANG, String PROXYNAME, int PROXYPORT, String API_KEY){ if (API_KEY.length()==32) { this.ForecastIOApiKey = API_KEY; this.forecast = new JsonObject(); this.currently = new JsonObject(); this.minutely = new JsonObject(); this.hourly = new JsonObject(); this.daily = new JsonObject(); this.flags = new JsonObject(); this.alerts = new JsonArray(); this.timeURL = null; this.excludeURL = null; this.extend = false; this.proxy_to_use = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXYNAME, PROXYPORT)); this.setUnits(UNITS); this.setLang(LANG); getForecast(LATITUDE, LONGITUDE); } else { System.err.println("The API Key doesn't seam to be valid."); } }//construtor - end /** * Returns the latitude that is setted for the request. * @return A Double number with the latitude. */ public Double getLatitude(){ return this.forecast.get("latitude").asDouble(); } /** * Returns the longitude that is setted for the request. * @return A Double number with the longitude. */ public Double getLongitude(){ return this.forecast.get("longitude").asDouble(); } /** * Returns the timezone that is setted. * @return A String with the timezone. */ public String getTimezone(){ return this.forecast.get("timezone").asString(); } /** * Returns the time that is setted for the request. * @return A String with the time */ public String getTime() { return timeURL; } /** * Sets the time for the request. * Format should be passed as follows:<br> * [YYYY]-[MM]-[DD]T[HH]:[MM]:[SS]{+,-}[HH][MM]<br> * The last {+,-}[HH][MM] is the timezone<br> * Example:<br> * [2013-05-06T12:00:00-0400]<br> * For more information refer to the API Docs: * <a href="https://developer.forecast.io">https://developer.forecast.io</a> * @param time in the described format */ public void setTime(String time) { this.timeURL = time; } /** * Sets the time for the request. * Time is parsed as follows:<br> * [YYYY]-[MM]-[DD]T[HH]:[MM]:[SS]{+,-}[HH][MM]<br> * The last {+,-}[HH][MM] is the timezone<br> * Example:<br> * [2013-05-06T12:00:00-0400]<br> * For more information refer to the API Docs: * <a href="https://developer.forecast.io">https://developer.forecast.io</a> * @param time to be parsed */ public void setTime(Date time){ this.timeURL = dateFormat.format(time); } /** * Returns the excluded fields that are setted for the request. * @return A String with the fields excluded */ public String getExcludeURL() { return excludeURL; } /** * Sets the fields to be excluded in the request.<br> * Format should be as follows:<br> * [field1,field2,fieldN] with no spaces * For more information refer to the API Docs: * <a href="https://developer.forecast.io">https://developer.forecast.io</a> * @param excludeURL in the described format */ public void setExcludeURL(String excludeURL) { this.excludeURL = excludeURL; } /** * Sets the http-proxy to use. * @param PROXYNAME hostname or ip of the proxy to use (e.g. "127.0.0.1"). If proxyname equals null, no proxy will be used. * @param PROXYPORT port of the proxy to use (e.g. 8080) */ public void setHTTPProxy(String PROXYNAME, int PROXYPORT) { if (PROXYNAME == null) { this.proxy_to_use = null; } else { this.proxy_to_use = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXYNAME, PROXYPORT)); } } /** * Returns if the hourly report should be extended in the request. * @return true or false */ public boolean isExtend() { return extend; } /** * Sets if the hourly report should be extended in the request. * For more information refer to the API Docs: * <a href="https://developer.forecast.io">https://developer.forecast.io</a> * @param extend true or false to extend or not the request */ public void setExtend(boolean extend) { this.extend = extend; } /** * @return the connection timeout in milliseconds. */ public int getConnectTimeout() { return connectTimeout; } /** * Sets the connection timeout in milliseconds. * @param connectTimeout timeout in milliseonds. */ public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; } /** * @return the read timeout in milliseconds. */ public int getReadTimeout() { return readTimeout; } /** * Sets the read timeout in milliseconds. * @param readTimeout timeout in milliseonds. */ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } /** * Returns the timezone offset in an double * For more information refer to the API Docs: * <a href="https://developer.forecast.io">https://developer.forecast.io</a> * @return double with the offset */ public double offsetValue(){ return this.forecast.get("offset").asDouble(); } /** * Returns the offset as a String with '+' or '-' sign. * For more information refer to the API Docs: * <a href="https://developer.forecast.io">https://developer.forecast.io</a> * @return String with the offset */ public String offset(){ if(this.forecast.get("offset").asDouble()<0) return ""+this.forecast.get("offset").asDouble(); else if(this.forecast.get("offset").asDouble()>0) return ""+"+"+this.forecast.get("offset").asDouble(); else return ""; } /** * Returns the units that are set in the request. * @return String with the units set */ public String getUnits(){ return this.unitsURL; } /** * Sets the units to be passed in the request. If the units are unavailable, auto is set.<br> * Units can be set with constants like ForecastIO.UNITS_AUTO. * For more information refer to the API Docs: * <a href="https://developer.forecast.io">https://developer.forecast.io</a> * @param units the units to be set. Units can set with a constant like ForecastIO.UNITS_SI. * (If the units are invalid the API will return an error) */ public void setUnits(String units){ this.unitsURL = units; } /** * Returns the language that are set in the request. * @return String with the language set */ public String getLang(){ return this.langURL; } /** * Sets the language to be passed in the request. If the language is not unavailable, english is set.<br> * Units can be set with constants like ForecastIO.LANG_ENGLISH. * For more information refer to the API Docs: * <a href="https://developer.forecast.io">https://developer.forecast.io</a> * @param lang the language to set. this can be set with the constant ForecastIO.LANG_ENGLISH (example for english). * If a constant is not available for a given an available API language it can be set with the language code. * (If the language is invalid the API will return an error) */ public void setLang(String lang){ this.langURL = lang; } /** * Returns the currently data point * @return JsonObject with the data point */ public JsonObject getCurrently(){ return this.currently; } /** * Returns the minutely data block * @return JsonObject with the data block */ public JsonObject getMinutely(){ return this.minutely; } /** * Returns the hourly data block * @return JsonObject with the data block */ public JsonObject getHourly(){ return this.hourly; } /** * Returns the flags data * @return JsonObject with the data */ public JsonObject getFlags(){ return this.flags; } /** * Returns the alerts data * @return JsonObject with the data */ public JsonArray getAlerts(){ return this.alerts; } /** * Returns the daily data block * @return JsonObject with the data block */ public JsonObject getDaily(){ return this.daily; } /** * Checks if there is any currently data available * @return true or false */ public boolean hasCurrently(){ if(this.currently == null) return false; else return true; } /** * Checks if there is any minutely data available * @return true or false */ public boolean hasMinutely(){ if(this.minutely == null) return false; else return true; } /** * Checks if there is any hourly data available * @return true or false */ public boolean hasHourly(){ if(this.hourly == null) return false; else return true; } /** * Checks if there is any daily data available * @return true or false */ public boolean hasDaily(){ if(this.daily == null) return false; else return true; } /** * Checks if there is any flags data available * @return true or false */ public boolean hasFlags(){ if(this.flags == null) return false; else return true; } /** * Checks if there is any flags data available * @return true or false */ public boolean hasAlerts(){ if(this.alerts == null) return false; else return true; } private String urlBuilder(String LATITUDE, String LONGITUDE){ StringBuilder url = new StringBuilder(""); url.append(ForecastIOURL); url.append(ForecastIOApiKey+"/"); url.append(LATITUDE.trim()+","+LONGITUDE.trim()); if(timeURL!=null) url.append(","+timeURL.trim()); url.append("?units="+unitsURL.trim()); url.append("&lang="+langURL.trim()); if(excludeURL!=null) url.append("&exclude="+excludeURL.trim()); if(extend) url.append("&extend=hourly"); return url.toString(); } /** * Does another query to the API and updates the data * This only updates the data in ForecastIO class * @return True if successful */ public boolean update(){ boolean b = getForecast(String.valueOf(getLatitude()), String.valueOf(getLongitude())); return b; } /** * Gets the forecast reports for the given coordinates with the set options * @param LATITUDE the geographical latitude * @param LONGITUDE the geographical longitude * @return True if successful */ public boolean getForecast(String LATITUDE, String LONGITUDE) { try { String reply = httpGET( urlBuilder(LATITUDE, LONGITUDE) ); if(reply == null) return false; this.forecast = Json.parse(reply).asObject(); //this.forecast = JsonObject.readFrom(reply); } catch (NullPointerException e) { System.err.println("Unable to connect to the API: "+e.getMessage()); return false; } return getForecast(this.forecast); }//getForecast - end /* * This change was suggested and made by github user brobzilla to add * the ability to use an external http library. I found this to be a * nice suggestion and improvement. However, because http libraries * usually return the raw string response, I find that it would be * useful to add a getForecast method that receives the response * String as parameter. */ /** * Parses the forecast reports for the given coordinates with the set options * Useful to use with an external http library * @param http_response String * @return boolean */ public boolean getForecast(String http_response) { this.forecast = Json.parse(http_response).asObject(); //this.forecast = JsonObject.readFrom(http_response); return getForecast(this.forecast); } /** * Parses the forecast reports for the given coordinates with the setted options * Useful to use with an external http library * Hint: The getForecast(String http_response) could be more useful since it receives * the raw response String instead of the JsonObect. * @param forecast JsonObject * @return true if successful */ public boolean getForecast(JsonObject forecast) { this.forecast = forecast; try { this.currently = forecast.get("currently").asObject(); } catch (NullPointerException e) { this.currently = null; } try { this.minutely = forecast.get("minutely").asObject(); } catch (NullPointerException e) { this.minutely = null; } try { this.hourly = forecast.get("hourly").asObject(); } catch (NullPointerException e) { this.hourly = null; } try { this.daily = forecast.get("daily").asObject(); } catch (NullPointerException e) { this.daily = null; } try { this.flags = forecast.get("flags").asObject(); } catch (NullPointerException e) { this.flags = null; } try { this.alerts = forecast.get("alerts").asArray(); } catch (NullPointerException e) { this.alerts = null; } return true; }//getForecast - end /** * Returns the url that is created by internal UrlBuilder method. * Useful to use with an external http library * * @param LATITUDE the geographical latitude * @param LONGITUDE the geographical longitude * @return url string. */ public String getUrl(String LATITUDE, String LONGITUDE) { return urlBuilder(LATITUDE, LONGITUDE); } /** * Returns the Cache-Control response header value * @return the string with the header value */ public String getHeaderCache_Control() { return Cache_Control; } /** * Returns the Expires response header value * @return the string with the header value */ public String getHeaderExpires() { return Expires; } /** * Returns the X-Forecast-API-Calls response header value<br> * This is the number os API calls made today from one given API Key. * @return the string with the header value */ public String getHeaderX_Forecast_API_Calls() { return X_Forecast_API_Calls; } /** * Returns the X-Response-Time response header value * @return the string with the header value */ public String getHeaderX_Response_Time() { return X_Response_Time; } /** * Returns the raw JSON response * @return the string with the JSON response */ public String getRawResponse() { return rawResponse; } private String httpGET(String requestURL) { //Variables URL request = null; HttpURLConnection connection = null; //Scanner scanner = null; BufferedReader reader = null; String s = ""; String response = ""; try { request = new URL(requestURL); // check, if a proxy was defined, if so, use it for the connection if (this.proxy_to_use != null) { connection = (HttpURLConnection) request.openConnection(this.proxy_to_use); } else { connection = (HttpURLConnection) request.openConnection(); } connection.setRequestMethod("GET"); connection.setUseCaches(false); connection.setDoInput(true); connection.setDoOutput(false); connection.setRequestProperty("Accept-Encoding", "gzip, deflate"); connection.setConnectTimeout(connectTimeout); connection.setReadTimeout(readTimeout); connection.connect(); Cache_Control = connection.getHeaderField("Cache-Control"); Expires = connection.getHeaderField("Expires"); X_Forecast_API_Calls = connection.getHeaderField("X-Forecast-API-Calls"); X_Response_Time = connection.getHeaderField("X-Response-Time"); if(connection.getResponseCode() == HttpURLConnection.HTTP_OK){ //obtain the encoding returned by the server String encoding = connection.getContentEncoding(); try { //create the appropriate stream wrapper based on the encoding type //use UTF-8 when parsing the JSON responses if (encoding != null && encoding.equalsIgnoreCase("gzip")) { reader = new BufferedReader(new InputStreamReader( new GZIPInputStream( connection.getInputStream() ), "UTF-8")); } else if (encoding != null && encoding.equalsIgnoreCase("deflate")) { reader = new BufferedReader(new InputStreamReader( new InflaterInputStream( connection.getInputStream(), new Inflater(true) ), "UTF-8")); } else { reader = new BufferedReader(new InputStreamReader( connection.getInputStream(),"UTF-8" )); } while( (s = reader.readLine()) != null ) response = s; } catch (IOException e){ System.err.println("Error: "+e.getMessage()); } finally { if (reader != null) { try { reader.close(); reader = null; } catch (IOException e) { System.err.println("Error: "+e.getMessage()); } } } } //if HTTP_OK - End // else if HttpURLConnection Not Ok else { try { reader = new BufferedReader(new InputStreamReader( connection.getErrorStream() )); while( (s = reader.readLine()) != null ) response = s; } catch (IOException e){ System.err.println("Error: "+e.getMessage()); } finally { if (reader != null) { try { reader.close(); reader = null; } catch (IOException e) { System.err.println("Error: "+e.getMessage()); } } } //If response is not ok print error and return null System.err.println("Bad Response: " + response + "\n"); return null; } //else if HttpURLConnection Not Ok - End } catch (IOException e) { System.err.println("Error: "+e.getMessage()); response = null; } finally { connection.disconnect(); } rawResponse = response; return response; }//httpGET - end }//public class - end