Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
rlanning2
OpenWeather REST and file connector
Commits
e32067f9
Commit
e32067f9
authored
Oct 27, 2021
by
Christopher Bohn
🤔
Browse files
Added scaffolding for reports with multiple timestamps
parent
317cc4c8
Changes
7
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
e32067f9
...
...
@@ -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
pom.xml
View file @
e32067f9
...
...
@@ -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>
...
...
src/main/java/edu/unl/cse/soft160/json_connections/Main.java
View file @
e32067f9
...
...
@@ -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
());
...
...
src/main/java/edu/unl/cse/soft160/json_connections/connector/OpenWeatherConnector.java
View file @
e32067f9
...
...
@@ -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
(
categorie
s
);
return
List
.
copyOf
(
description
s
);
}
/**
...
...
src/main/resources/air_pollution/.gitkeep
→
src/main/resources/air_pollution/
forecast/
.gitkeep
View file @
e32067f9
File moved
src/main/resources/air_pollution/history/.gitkeep
0 → 100644
View file @
e32067f9
src/test/java/edu/unl/cse/soft160/json_connections/UnlOct24At0917Test.java
View file @
e32067f9
...
...
@@ -43,7 +43,7 @@ public class UnlOct24At0917Test {
@Test
public
void
testWeather
()
{
List
<
OpenWeatherConnector
.
WeatherCategory
>
expectedCategories
=
Arrays
.
as
List
(
List
<
OpenWeatherConnector
.
WeatherCategory
>
expectedCategories
=
List
.
of
(
OpenWeatherConnector
.
WeatherCategory
.
MIST
,
OpenWeatherConnector
.
WeatherCategory
.
RAIN
,
OpenWeatherConnector
.
WeatherCategory
.
THUNDERSTORM
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment