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

Added scaffolding for reports with multiple timestamps

parent 317cc4c8
......@@ -74,6 +74,7 @@ code that uses data from [OpenWeathermap.org](https://openweathermap.org).
forecast data set
- [*air_pollution*](https://openweathermap.org/api/air-pollution), the air
quality and pollution data set
- Includes *air_pollution/forecast* and *air_pollution/history*
6. The remaining `OpenWeatherConnector` methods provide typed values for the
JSON fields in the data loaded by `retrieveData(String)`.
......@@ -94,4 +95,7 @@ code that uses data from [OpenWeathermap.org](https://openweathermap.org).
not needed for our intended use of `OpenWeatherConnector`. (Also,
`RestConnection` does not (yet) support PUSH and PUT calls, which would be
required for those APIs.)
- I need to write some tests.
\ No newline at end of file
- Three-hour rain (and three-hour snow) total defaults to the 1-hour rain (snow)
when a separate 3-hour value is not reported. Recent real-world data calls
into question the assumption that a 3-hour value will be reported when rain
has fallen for more than an hour (or even more than three hours)
\ No newline at end of file
......@@ -18,8 +18,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
......@@ -28,8 +28,8 @@
<version>3.3.1</version>
<configuration>
<show>public</show>
<source>8</source>
<target>8</target>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
......
......@@ -16,11 +16,11 @@ public class Main {
System.err.println("Caused by: " + ioException.getCause());
System.err.println("\t" + ioException.getCause().getMessage());
}
OpenWeatherConnector weather = new OpenWeatherConnector("weather", apiKey);
// OpenWeatherConnector weather = new OpenWeatherConnector("weather");
// OpenWeatherConnector weather = new OpenWeatherConnector("weather", apiKey);
OpenWeatherConnector weather = new OpenWeatherConnector("air_pollution/history", apiKey);
String data = null;
try {
data = weather.retrieveData("zip=68588");
data = weather.retrieveData("lat=40.8194698&lon=-96.7066454&start=1606223802&end=1606482999");
// data = weather.retrieveData("website-example.json");
} catch (IOException ioException) {
System.err.println("IO Exception: " + ioException.getClass());
......
......@@ -13,6 +13,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Wrapper for {@link Connection}s to OpenWeather data sources. Provides methods to extract values from OpenWeather
......@@ -50,56 +52,50 @@ public class OpenWeatherConnector {
}
/**
* Convenience enums corresponding to documented OpenWeathermap APIs.
* Supported data sets, a subset of the documented OpenWeathermap APIs.
*/
protected enum DataSet {
WEATHER, ONECALL, FORECAST, AIR_POLLUTION, DIRECT, REVERSE,
// STATIONS, TRIGGERS, // PUT/POST calls
// MAP, // map certainly isn't JSON, but it also appears not to be a REST call
}
public static final Set<String> allowableDataSets = Set.of(
"weather",
"onecall",
"forecast",
"air_pollution", "air_pollution/forecast", "air_pollution/history",
"direct", "reverse" // geocoding
// "stations", "triggers" excluded because PUT/POST calls are not supported here
// "map" excluded because it isn't JSON and also appears not to be a REST call
);
public static final Date IMPOSSIBLE_DATE = new Date(Long.MIN_VALUE);
public static final String PROTOCOL = "http";
public static final String REST_AUTHORITY = "api.openweathermap.org";
protected static String REST_PATH; // Should be able to declare this as final, but static analysis doesn't see that
protected final Connection dataSource;
protected final DataSet dataSet;
protected String dataSet;
protected String REST_PATH; // Should be able to declare this as final, but static analysis doesn't see that
protected JSONObject data;
protected Date currentTimestamp;
/**
* Constructor to be used when connecting to OpenWeather data stored in a local file.
* <p>Constructor to be used when connecting to OpenWeather data stored in a local file.</p>
*
* <p><i>Note</i>: in the case of Air Pollution Forecast and Air Pollution History, the data set is of the form
* "air_pollution/forecast" and "air_pollution/history".</p>
*
* @param fileDataSet directory containing the specified file; must correspond to one of the
* <a href="https://openweathermap.org/api">OpenWeathermap.org APIs</a>
* @throws IllegalArgumentException if {@code fileDataSet} does not correspond to an OpenWeathermap.org API
*/
public OpenWeatherConnector(String fileDataSet) throws IllegalArgumentException {
data = null;
try {
dataSet = DataSet.valueOf(fileDataSet.toUpperCase());
} catch (IllegalArgumentException illegalArgumentException) {
throw new IllegalArgumentException(fileDataSet + " is not a valid data set.", illegalArgumentException);
}
switch (dataSet) {
case WEATHER:
case ONECALL:
case FORECAST:
case AIR_POLLUTION:
REST_PATH = "/data/2.5/";
break;
case DIRECT:
case REVERSE:
REST_PATH = "/geo/1.0/";
break;
default:
REST_PATH = "unknown";
}
initializeFields(fileDataSet);
dataSource = new FileConnection(fileDataSet);
}
/**
* Constructor to be used when connecting to <a href="https://openweathermap.org">OpenWeathermap.org</a>'s REST API.
* <p>Constructor to be used when connecting to <a href="https://openweathermap.org">OpenWeathermap.org</a>s's
* REST API.</p>
*
* <p><i>Note</i>: in the case of Air Pollution Forecast and Air Pollution History, the data set is of the form
* "air_pollution/forecast" and "air_pollution/history".</p>
*
* @param restDataSet specific <a href="https://openweathermap.org/api">OpenWeathermap.org API</a> to be used
* @param apiKey token provided by <a href="https://openweathermap.org">OpenWeathermap.org</a>
......@@ -107,27 +103,33 @@ public class OpenWeatherConnector {
* @throws IllegalArgumentException if {@code fileDataSet} does not correspond to an OpenWeathermap.org API
*/
public OpenWeatherConnector(String restDataSet, String apiKey) throws IllegalArgumentException {
data = null;
try {
dataSet = DataSet.valueOf(restDataSet.toUpperCase());
} catch (IllegalArgumentException illegalArgumentException) {
throw new IllegalArgumentException(restDataSet + " is not a valid data set.", illegalArgumentException);
initializeFields(restDataSet);
dataSource = new RestConnection(PROTOCOL, REST_AUTHORITY, REST_PATH + restDataSet, apiKey);
}
private void initializeFields(String intendedDataSet) {
if(!allowableDataSets.contains(intendedDataSet)) {
throw new IllegalArgumentException(intendedDataSet + " is not a valid data set.");
}
dataSet = intendedDataSet;
switch (dataSet) {
case WEATHER:
case ONECALL:
case FORECAST:
case AIR_POLLUTION:
case "weather":
case "onecall":
case "forecast":
case "air_pollution":
case "air_pollution/forecast":
case "air_pollution/history":
REST_PATH = "/data/2.5/";
break;
case DIRECT:
case REVERSE:
case "direct":
case "reverse":
REST_PATH = "/geo/1.0/";
break;
default:
REST_PATH = "unknown";
}
dataSource = new RestConnection(PROTOCOL, REST_AUTHORITY, REST_PATH + restDataSet, apiKey);
data = null;
currentTimestamp = null;
}
/**
......@@ -147,7 +149,7 @@ public class OpenWeatherConnector {
data = dataSource.get(requestedData);
} catch (FileNotFoundException fileNotFoundException) {
if (dataSource instanceof RestConnection) {
throw new IOException("Could not access \"" + dataSet.toString().toLowerCase()
throw new IOException("Could not access \"" + dataSet.toLowerCase()
+ "\" dataset at " + REST_AUTHORITY + ".",
fileNotFoundException);
} else {
......@@ -166,20 +168,22 @@ public class OpenWeatherConnector {
if (dataSource instanceof FileConnection) {
message = "Error while parsing file " + requestedData + ".";
} else if (dataSource instanceof RestConnection) {
message = "Error while parsing the " + dataSet.toString().toLowerCase()
message = "Error while parsing the " + dataSet.toLowerCase()
+ " response from " + REST_AUTHORITY + ".";
} else {
message = "Error while parsing requested data.";
}
throw new IOException(message, parseException);
}
long unixTime = extractLongValue("dt", extractLongValue("current", "dt", Long.MIN_VALUE));
currentTimestamp = unixTime == Long.MIN_VALUE ? IMPOSSIBLE_DATE : new Date(unixTime * 1000);
return data.toJSONString();
}
private void checkForDataPresence() throws IllegalStateException {
if (data == null) {
throw new IllegalStateException("No data has been retrieved from the "
+ dataSet.toString().toLowerCase() + " dataset.");
+ dataSet.toLowerCase() + " dataset.");
}
}
......@@ -258,7 +262,7 @@ public class OpenWeatherConnector {
}
/**
* Provides the date and time for the weather.
* Provides the date and time for the current weather, or {@link #IMPOSSIBLE_DATE} if no valid timestamp exists.
*
* @return timestamp for the weather observations
* @throws IllegalStateException if no data has been retrieved using {@link #retrieveData(String)}
......@@ -266,8 +270,28 @@ public class OpenWeatherConnector {
* @see #getLongitude()
*/
public Date getTimestamp() throws IllegalStateException {
long unixTime = extractLongValue("dt", Long.MIN_VALUE);
return new Date(unixTime * 1000);
checkForDataPresence();
return currentTimestamp;
}
public List<Date> getTimeStamps() throws IllegalStateException {
checkForDataPresence();
Set<String> possibleListNames = Set.of("list", "minutely", "hourly", "daily");
List<Date> timestamps = new ArrayList<>();
for (String possibleListName : possibleListNames) {
if (data.containsKey(possibleListName)) {
JSONArray entries = (JSONArray)data.get(possibleListName);
if (entries != null) {
timestamps = new ArrayList<>(entries.size());
for (Object entry : entries) {
long unixTime = (Long)((JSONObject)entry).get("dt");
timestamps.add(new Date(unixTime));
}
}
}
}
timestamps.sort(null);
return List.copyOf(timestamps);
}
/**
......@@ -282,16 +306,21 @@ public class OpenWeatherConnector {
public List<WeatherCategory> getWeatherCategories() throws IllegalStateException {
checkForDataPresence();
JSONArray weather = (JSONArray)data.get("weather");
List<WeatherCategory> categories = new ArrayList<>(weather.size());
for (Object condition : weather) {
String category = ((JSONObject)condition).get("main").toString();
try {
categories.add(WeatherCategory.valueOf(category.toUpperCase()));
} catch (IllegalArgumentException ignored) {
categories.add(WeatherCategory.UNKNOWN_CATEGORY);
List<WeatherCategory> categories;
if (weather != null) {
categories = new ArrayList<>(weather.size());
for (Object condition : weather) {
String category = ((JSONObject)condition).get("main").toString();
try {
categories.add(WeatherCategory.valueOf(category.toUpperCase()));
} catch (IllegalArgumentException ignored) {
categories.add(WeatherCategory.UNKNOWN_CATEGORY);
}
}
} else {
categories = new ArrayList<>(0);
}
return Collections.unmodifiableList(categories);
return List.copyOf(categories);
}
/**
......@@ -306,11 +335,16 @@ public class OpenWeatherConnector {
public List<String> getWeatherDescriptions() throws IllegalStateException {
checkForDataPresence();
JSONArray weather = (JSONArray)data.get("weather");
List<String> categories = new ArrayList<>(weather.size());
for (Object condition : weather) {
categories.add(((JSONObject)condition).get("description").toString());
List<String> descriptions;
if (weather != null) {
descriptions = new ArrayList<>(weather.size());
for (Object condition : weather) {
descriptions.add(((JSONObject)condition).get("description").toString());
}
} else {
descriptions = new ArrayList<>(0);
}
return Collections.unmodifiableList(categories);
return List.copyOf(descriptions);
}
/**
......
......@@ -43,7 +43,7 @@ public class UnlOct24At0917Test {
@Test
public void testWeather() {
List<OpenWeatherConnector.WeatherCategory> expectedCategories = Arrays.asList(
List<OpenWeatherConnector.WeatherCategory> expectedCategories = List.of(
OpenWeatherConnector.WeatherCategory.MIST,
OpenWeatherConnector.WeatherCategory.RAIN,
OpenWeatherConnector.WeatherCategory.THUNDERSTORM
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment