Commit bf42ba6b authored by Christopher Bohn's avatar Christopher Bohn 🤔
Browse files

Can now handle "onecall" data set.

Still need to improve error handling: NullPointerExceptions are possible
when invoking a call for a non-present list.
parent 11fdab00
......@@ -83,14 +83,10 @@ code that uses data from [OpenWeathermap.org](https://openweathermap.org).
- `OpenWeatherConnector` only supports the free, no-cost APIs provided by
[OpenWeathermap.org](https://openweathermap.org). We do not have plans to
support the subscription-based APIs.
- As of this writing, `OpenWeatherConnector` only supports the *"weather"*,
*"forecast*, *"air_pollution"*, *"air_pollution/forecast"*, and
*"air_pollution/history"* datasets. We have support for the current and
minutely data in the *"onecall"* dataset and are working on hourly and daily
data.
- We do not currently access the NWS alerts from the *onecall* dataset but will
do so soon.
- We not currently support the *onecall/timemachine* dataset but will do so soon.
- We may access sunset, sunrise, moonset, moonrise, and moon phase if it's needed.
- We may add support for the
[Geocoding API](https://openweathermap.org/api/geocoding-api) (*"direct"* and
*"reverse"* data sets).
......
......@@ -133,12 +133,57 @@ public class Demonstration {
System.out.println("The UV Index is " + weather.getUltravioletIndex());
System.out.println("Precipitation in the last hour has been " + weather.getOneHourRainfall() + "mm of" +
" rain and " + weather.getOneHourSnowfall() + "mm of snow.");
System.out.println("In 30 minutes...");
timestamp = weather.getTimestamps().get(weather.getTimestamps("minutely").size() / 2);
System.out.println("There is minutely data from " + weather.getTimestamps("minutely").get(0) + " to " +
weather.getTimestamps("minutely").get(weather.getTimestamps("minutely").size() - 1));
timestamp = weather.getTimestamps("minutely").get(weather.getTimestamps("minutely").size() / 2);
System.out.println("\tAt " + timestamp + " the 1-hour (?) precipitation total will be " +
weather.getMinutelyPrecipitation(timestamp));
System.out.println("In 12 hours... (standby)");
System.out.println("In 4 days... (standby)");
System.out.println("There is hourly data from " + weather.getTimestamps("hourly").get(0) + " to " +
weather.getTimestamps("hourly").get(weather.getTimestamps("hourly").size() - 1));
timestamp = weather.getTimestamps("hourly").get(weather.getTimestamps("hourly").size() / 2);
System.out.println("\tAt " + timestamp + " it will be " + weather.getWeatherCategories(timestamp));
System.out.println("\tSpecifically, it will be " + weather.getWeatherDescriptions(timestamp));
System.out.println("\tThe temperature will be " + weather.getTemperature(timestamp) + "K");
System.out.println("\tFactoring in the relative humidity of " + weather.getHumidity(timestamp) + "%, the dew " +
"point will be " + weather.getDewPoint(timestamp) + "K, and it will feel like " +
weather.getFeelsLike(timestamp) + "K");
System.out.println("\tThat probably also factored in winds, which will be " +
weather.getWindSpeed(timestamp) + "m/s from " + weather.getWindDirection(timestamp) +
"˚, gusting to " + weather.getWindGust(timestamp) + "m/s");
System.out.println("\tVisibility will be " + weather.getVisibility(timestamp) + "m");
System.out.println("\tThe pressure will be " + weather.getPressure(timestamp) + "hPa");
System.out.println("\tCloud cover will be " + weather.getCloudCover(timestamp) + "%");
System.out.println("\tThe UV Index will be " + weather.getUltravioletIndex(timestamp));
System.out.println("\tThere will be a " + weather.getProbabilityOfPrecipitation(timestamp) * 100 + "% " +
"chance of precipitation, resulting in " + weather.getOneHourRainfall(timestamp) + "mm of " +
"rain and " + weather.getOneHourSnowfall(timestamp) + "mm of snow in the hour before " +
timestamp);
System.out.println("There is daily data from " + weather.getTimestamps("daily").get(0) + " to " +
weather.getTimestamps("daily").get(weather.getTimestamps("daily").size() - 1));
timestamp = weather.getTimestamps("daily").get(weather.getTimestamps("daily").size() / 2);
System.out.println("\tAt " + timestamp + " it will be " + weather.getWeatherCategories(timestamp));
System.out.println("\tSpecifically, it will be " + weather.getWeatherDescriptions(timestamp));
System.out.println("\tThe temperatures will range from " + weather.getLowTemperature(timestamp) + "K to " +
weather.getHighTemperature(timestamp) + "K");
System.out.println("\t\tMorning:" + weather.getMorningTemperature(timestamp) + "K");
System.out.println("\t\tDaytime:" + weather.getDaytimeTemperature(timestamp) + "K");
System.out.println("\t\tEvening:" + weather.getEveningTemperature(timestamp) + "K");
System.out.println("\t\tNighttime:" + weather.getNighttimeTemperature(timestamp) + "K");
System.out.println("\tFactoring in the relative humidity of " + weather.getHumidity(timestamp) + "%, the dew " +
"point will be " + weather.getDewPoint(timestamp) + "K, and it will feel like:");
System.out.println("\t\tMorning:" + weather.getMorningFeelsLike(timestamp) + "K");
System.out.println("\t\tDaytime:" + weather.getDaytimeFeelsLike(timestamp) + "K");
System.out.println("\t\tEvening:" + weather.getEveningFeelsLike(timestamp) + "K");
System.out.println("\t\tNighttime:" + weather.getNighttimeFeelsLike(timestamp) + "K");
System.out.println("\tThat probably also factored in winds, which will be " +
weather.getWindSpeed(timestamp) + "m/s from " + weather.getWindDirection(timestamp) +
"˚, gusting to " + weather.getWindGust(timestamp) + "m/s");
System.out.println("\tThe pressure will be " + weather.getPressure(timestamp) + "hPa");
System.out.println("\tCloud cover will be " + weather.getCloudCover(timestamp) + "%");
System.out.println("\tThe UV Index will be " + weather.getUltravioletIndex(timestamp));
System.out.println("\tThere will be a " + weather.getProbabilityOfPrecipitation(timestamp) * 100 + "% " +
"chance of precipitation, resulting in " + weather.getDailyRainfall(timestamp) + "mm of " +
"rain and " + weather.getDailySnowfall(timestamp) + "mm of snow");
}
private static void reportForecastData(OpenWeatherConnector weather) {
......
......@@ -635,6 +635,93 @@ public class OpenWeatherConnector {
}
}
/**
* <p>Provides the forecasted morning temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted morning temperature, or {@link Double#NaN} if morning temperature is not in the forecast
*/
public double getMorningTemperature(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "temp", "morn", Double.NaN);
}
/**
* <p>Provides the forecasted daytime temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted daytime temperature, or {@link Double#NaN} if daytime temperature is not in the forecast
*/
public double getDaytimeTemperature(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "temp", "day", Double.NaN);
}
/**
* <p>Provides the forecasted evening temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted evening temperature, or {@link Double#NaN} if evening temperature is not in the forecast
*/
public double getEveningTemperature(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "temp", "eve", Double.NaN);
}
/**
* <p>Provides the forecasted nighttime temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted nighttime temperature, or {@link Double#NaN} if nighttime temperature is not in the
* forecast
*/
public double getNighttimeTemperature(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "temp", "night", Double.NaN);
}
/**
* <p>Provides the forecasted daily low temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted daily low temperature, or {@link Double#NaN} if daily low temperature is not in the
* forecast
*/
public double getLowTemperature(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "temp", "min", Double.NaN);
}
/**
* <p>Provides the forecasted daily high temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted daily high temperature, or {@link Double#NaN} if daily high temperature is not in the
* forecast
*/
public double getHighTemperature(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "temp", "max", Double.NaN);
}
/**
* <p>Provides the current relative humidity (percent of saturation).</p>
* <p>Available for the "weather" and "onecall" datasets.</p>
......@@ -678,6 +765,20 @@ public class OpenWeatherConnector {
return extractDoubleFromJSON(data, "current", "dew_point", Double.NaN);
}
/**
* <p>Provides the forecasted dew point in the default <a href="https://openweathermap.org/current#data">units</a>
* or those specified in the call to {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted dew point, or {@link Double#NaN} if dew point is not in the forecast
*/
public double getDewPoint(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry(getTimestamps("hourly").contains(timestamp) ? "hourly" : "daily",
timestamp), "dew_point", Double.NaN);
}
/**
* <p>Provides the current "feels like" temperature in the default
* <a href="https://openweathermap.org/current#data">units</a>
......@@ -714,6 +815,67 @@ public class OpenWeatherConnector {
}
}
/**
* <p>Provides the forecasted morning "feels like" temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted morning "feels like" temperature, or {@link Double#NaN} if morning "feels like"
* temperature is not in the forecast
*/
public double getMorningFeelsLike(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "feels_like", "morn", Double.NaN);
}
/**
* <p>Provides the forecasted "feels like" daytime temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted daytime "feels like" temperature, or {@link Double#NaN} if daytime "feels like"
* temperature is not in the forecast
*/
public double getDaytimeFeelsLike(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "feels_like", "day", Double.NaN);
}
/**
* <p>Provides the forecasted evening "feels like" temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted evening "feels like" temperature, or {@link Double#NaN} if evening "feels like"
* temperature is not in the forecast
*/
public double getEveningFeelsLike(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "feels_like", "eve", Double.NaN);
}
/**
* <p>Provides the forecasted nighttime "feels like" temperature in the default
* <a href="https://openweathermap.org/current#data">units</a> or those specified in the call to
* {@link #retrieveData(String)}.</p>
* <p>Available for the "onecall" (daily only) dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted nighttime "feels like" temperature, or {@link Double#NaN} if nighttime "feels like"
* temperature is not in the
* forecast
*/
public double getNighttimeFeelsLike(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "feels_like", "night", Double.NaN);
}
/**
* <p>Provides the current pressure in the default <a href="https://openweathermap.org/current#data">units</a>
* or those specified in the call to {@link #retrieveData(String)}.</p>
......@@ -864,6 +1026,19 @@ public class OpenWeatherConnector {
return extractDoubleFromJSON(data, "current", "uvi", Double.NaN);
}
/**
* <p>Provides the forecasted UV Index.</p>
* <p>Available for the "onecall" dataset.</p>
*
* @param timestamp the date/time corresponding to the desired data
* @return the forecasted UV Index, or {@link Double#NaN} if the UV Index is not in the forecast
*/
public double getUltravioletIndex(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry(getTimestamps("hourly").contains(timestamp) ? "hourly" : "daily",
timestamp), "uvi", Double.NaN);
}
/**
* <p>Provides the current cloud cover percentage (cloudiness).</p>
* <p>Available for the "weather" and "onecall" datasets.</p>
......@@ -925,6 +1100,18 @@ public class OpenWeatherConnector {
"rain", "1h", 0.0);
}
/**
* <p>Provides the total rain column, in millimeters, forecasted for the one hour before the specified
* timestamp.</p>
* <p>Available for the "onecall" (hourly only) dataset.</p>
*
* @return the forecasted one-hour rainfall total, or 0.0 if rain volume is not in the forecast
*/
public double getOneHourRainfall(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("hourly", timestamp), "rain", "1h", 0.0);
}
/**
* <p>Provides the three-hour rainfall total column, in millimeters.</p>
* <p>Available for the "weather" dataset.</p>
......@@ -960,6 +1147,18 @@ public class OpenWeatherConnector {
"snow", "1h", 0.0);
}
/**
* <p>Provides the total snow column, in millimeters, forecasted for the one hour before the specified
* timestamp.</p>
* <p>Available for the "onecall" (hourly only) dataset.</p>
*
* @return the forecasted one-hour rainfall total, or 0.0 if rain volume is not in the forecast
*/
public double getOneHourSnowfall(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("hourly", timestamp), "snow", "1h", 0.0);
}
/**
* <p>Provides the three-hour snowfall total column, in millimeters.</p>
* <p>Available for the "weather" dataset.</p>
......@@ -984,8 +1183,9 @@ public class OpenWeatherConnector {
}
/**
* <p>Provides the precipitation column, in millimeters, forecasted for the the specified minute. This appears to be
* a 1-hour total that does not distinguish between rain and snow.</p>
* <p>Provides the precipitation column, in millimeters, forecasted for the the specified minute. Not explicitly
* stated in the API specification, this appears to be a 1-hour total that does not distinguish between rain and
* snow.</p>
* <p>Available for the "onecall" dataset.</p>
*
* @return the forecasted precipitation total, or 0.0 if precipitation volume is not in the forecast
......@@ -995,6 +1195,30 @@ public class OpenWeatherConnector {
return extractDoubleFromJSON(getListEntry("minutely", timestamp), "precipitation", 0.0);
}
/**
* <p>Provides the rain column, in millimeters, forecasted for the the specified day. Not explicitly stated in the
* API specification, this appears to be a daily total.</p>
* <p>Available for the "onecall" dataset.</p>
*
* @return the forecasted rain total, or 0.0 if precipitation volume is not in the forecast
*/
public double getDailyRainfall(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "rain", 0.0);
}
/**
* <p>Provides the snow column, in millimeters, forecasted for the the specified day. Not explicitly stated in the
* API specification, this appears to be a daily total.</p>
* <p>Available for the "onecall" dataset.</p>
*
* @return the forecasted snow total, or 0.0 if precipitation volume is not in the forecast
*/
public double getDailySnowfall(Date timestamp) {
checkForDataReadiness(Set.of("onecall"));
return extractDoubleFromJSON(getListEntry("daily", timestamp), "snow", 0.0);
}
/*
* Wrapper methods for Air Quality
*/
......
......@@ -117,7 +117,22 @@ public class ExceptionsTest {
connector.getWindGust(daily);
connector.getProbabilityOfPrecipitation(hourly);
connector.getCloudCover(daily);
// TODO: Introduce new methods
connector.getDailyRainfall(daily);
connector.getDailySnowfall(daily);
connector.getOneHourRainfall(hourly);
connector.getOneHourSnowfall(hourly);
connector.getUltravioletIndex(daily);
connector.getDewPoint(hourly);
connector.getMorningTemperature(daily);
connector.getDaytimeTemperature(daily);
connector.getEveningTemperature(daily);
connector.getNighttimeTemperature(daily);
connector.getLowTemperature(daily);
connector.getHighTemperature(daily);
connector.getMorningFeelsLike(daily);
connector.getDaytimeFeelsLike(daily);
connector.getEveningFeelsLike(daily);
connector.getNighttimeFeelsLike(daily);
// pass
}
......@@ -434,6 +449,86 @@ public class ExceptionsTest {
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getDailyRainfall(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getDailySnowfall(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getOneHourRainfall(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getOneHourSnowfall(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getDewPoint(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getUltravioletIndex(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getMorningTemperature(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getDaytimeTemperature(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getEveningTemperature(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getNighttimeTemperature(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getLowTemperature(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getHighTemperature(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getMorningFeelsLike(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getDaytimeFeelsLike(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getEveningFeelsLike(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
try {
connector.getNighttimeFeelsLike(timestamp);
fail(failMessage);
} catch (IllegalStateException ignored) {
}
// pass
}
......@@ -602,6 +697,86 @@ public class ExceptionsTest {
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getDailyRainfall(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getDailySnowfall(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getOneHourRainfall(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getOneHourSnowfall(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getDewPoint(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getUltravioletIndex(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getMorningTemperature(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getDaytimeTemperature(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getEveningTemperature(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getNighttimeTemperature(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getLowTemperature(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getHighTemperature(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getMorningFeelsLike(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getDaytimeFeelsLike(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getEveningFeelsLike(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getNighttimeFeelsLike(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
// pass
}
......@@ -775,6 +950,86 @@ public class ExceptionsTest {
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.getDailyRainfall(timestamp);
fail(failMessage);
} catch (UnsupportedOperationException ignored) {
}
try {
connector.