package com.hourlyweather.yrno.forecast; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.text.ParseException; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Hours; import org.joda.time.MutableDateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import com.hourlyweather.forecast.ForecastHour; import com.hourlyweather.forecast.HourlyForecast; import com.hourlyweather.yrno.XmlParserUtil; import com.hourlyweather.yrno.sunrise.SunriseFetcher; public class ForecastFetcher { private static final DateTimeFormatter df = DateTimeFormat.forPattern( "YYYY-MM-dd'T'HH':00:00Z'").withZone(DateTimeZone.UTC); /** * Connects to the yr.no api and returns an input stream for the hourly * forecast xml * * @param lat * the latitude your polling for * @param lon * the longitude your polling for * @return */ private static InputStream getHourlyForecastData(HourlyForecast forecast) { // pull the weather xml try { URL weatherUrl = new URL( "http://api.yr.no/weatherapi/locationforecast/1.8/?lat=" + forecast.getLat() + ";lon=" + forecast.getLon()); return weatherUrl.openStream(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * gets the current hourly forecast based on the supplied latitude and * longitude * * @param lat * @param lon * @param hours * the number of hours into the future to poll for * @return */ public static boolean getHourlyForecast(HourlyForecast forecast) { // add the sun rise/set times to the forecast SunriseFetcher.addSunlightDurationToForecast(forecast); InputStream input = getHourlyForecastData(forecast); DateTime endWindow; { // add a day plus our start time to create a cut off time MutableDateTime temp = forecast.getStart().toMutableDateTime(); temp.addHours(forecast.getHours()); temp.addDays(1); endWindow = temp.toDateTime(); } XmlPullParser xpp; try { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); xpp = factory.newPullParser(); } catch (XmlPullParserException e) { System.out.println("error creating xml parser: " + e.getMessage()); return false; } try { xpp.setInput(new BufferedReader(new InputStreamReader(input))); int eventType = xpp.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) if ("time".equalsIgnoreCase(xpp.getName())) if (!parseTimeTag(xpp, endWindow, forecast)) break; eventType = xpp.next(); } input.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return false; } return true; } /** * returns if the current XML pull parser tag is of type "time" and the from * attribute is outside of our range * * @param xpp * @param startHour * @param endHour * @return * @throws IOException * @throws XmlPullParserException * @throws ParseException */ private static boolean parseTimeTag(XmlPullParser xpp, DateTime endWindow, HourlyForecast forecast) throws XmlPullParserException, IOException, ParseException { DateTime from = df.parseDateTime( XmlParserUtil.getAttributeByName(xpp, "from")).withZone(forecast.getTimeZone()); // check if we are at the end of our time range if (from.isAfter(endWindow)) return false; DateTime to = df.parseDateTime( XmlParserUtil.getAttributeByName(xpp, "to")).withZone(forecast.getTimeZone()); // parse the child elements of the time element for the forecast details ForecastHour forecastHour = new ForecastHour(); for (int eventType = xpp.next(); !(eventType == XmlPullParser.END_TAG && "time" .equalsIgnoreCase(xpp.getName())); eventType = xpp.next()) if (eventType == XmlPullParser.START_TAG) // find all the weather symbols if ("symbol".equalsIgnoreCase(xpp.getName())) forecastHour.setSymbol(XmlParserUtil.getAttributeByName( xpp, "id")); else if ("precipitation".equalsIgnoreCase(xpp.getName())) { Double precipitation = getPrecipitation(from, to, XmlParserUtil.getAttributeByName(xpp, "value")); forecastHour.setPrecipitation(precipitation); } else if ("windspeed".equalsIgnoreCase(xpp.getName())) { Double windSpeed = getWindSpeed(from, to, XmlParserUtil.getAttributeByName(xpp, "mps")); forecastHour.setWindSpeed(windSpeed); } else if ("temperature".equalsIgnoreCase(xpp.getName())) // find the weather details forecastHour.setTemp(XmlParserUtil.getAttributeByName(xpp, "value")); forecast.add(from, to, forecastHour); return true; } private static Double getWindSpeed(DateTime from, DateTime to, String windSpeedString) { Double windSpeed; try { if (windSpeedString != null) windSpeed = Double.parseDouble(windSpeedString); else return null; } catch (NumberFormatException e) { // nothing we can do now, the api may have changed return null; } return windSpeed; } /** * gets the percipitation per hour based on the span and the converted * precipitation string * * @param from * @param to * @param precipitationString * @return */ private static Double getPrecipitation(DateTime from, DateTime to, String precipitationString) { Double precipitation; try { if (precipitationString != null) precipitation = Double.parseDouble(precipitationString); else return null; } catch (NumberFormatException e) { // nothing we can do now, the api may have changed return null; } return precipitation / Hours.hoursBetween(from, to).getHours(); } }