1 package uk
.me
.njae
.sunshine
;
3 import android
.content
.ContentUris
;
4 import android
.content
.ContentValues
;
5 import android
.content
.Context
;
6 import android
.database
.Cursor
;
7 import android
.net
.Uri
;
8 import android
.os
.AsyncTask
;
9 import android
.util
.Log
;
11 import org
.json
.JSONArray
;
12 import org
.json
.JSONException
;
13 import org
.json
.JSONObject
;
15 import java
.io
.BufferedReader
;
16 import java
.io
.IOException
;
17 import java
.io
.InputStream
;
18 import java
.io
.InputStreamReader
;
19 import java
.net
.HttpURLConnection
;
21 import java
.util
.Date
;
22 import java
.util
.Vector
;
24 import uk
.me
.njae
.sunshine
.data
.WeatherContract
;
25 import uk
.me
.njae
.sunshine
.data
.WeatherContract
.LocationEntry
;
26 import uk
.me
.njae
.sunshine
.data
.WeatherContract
.WeatherEntry
;
29 * Created by neil on 09/11/14.
33 public class FetchWeatherTask
extends AsyncTask
<String
, Void
, Void
> {
35 private final String LOG_TAG
= FetchWeatherTask
.class.getSimpleName();
37 private final Context mContext
;
39 public FetchWeatherTask(Context context
) {
44 * Helper method to handle insertion of a new location in the weather database.
46 * @param locationSetting The location string used to request updates from the server.
47 * @param cityName A human-readable city name, e.g "Mountain View"
48 * @param lat the latitude of the city
49 * @param lon the longitude of the city
50 * @return the row ID of the added location.
52 private long addLocation(String locationSetting
, String cityName
, double lat
, double lon
) {
54 // Log.v(LOG_TAG, "inserting " + cityName + ", with coord: " + lat + ", " + lon);
56 // First, check if the location with this city name exists in the db
57 Cursor cursor
= mContext
.getContentResolver().query(
58 LocationEntry
.CONTENT_URI
,
59 new String
[]{LocationEntry
._ID
},
60 LocationEntry
.COLUMN_LOCATION_SETTING
+ " = ?",
61 new String
[]{locationSetting
},
64 if (cursor
.moveToFirst()) {
65 // Log.v(LOG_TAG, "Found it in the database!");
66 int locationIdIndex
= cursor
.getColumnIndex(LocationEntry
._ID
);
67 return cursor
.getLong(locationIdIndex
);
69 // Log.v(LOG_TAG, "Didn't find it in the database, inserting now!");
70 ContentValues locationValues
= new ContentValues();
71 locationValues
.put(LocationEntry
.COLUMN_LOCATION_SETTING
, locationSetting
);
72 locationValues
.put(LocationEntry
.COLUMN_CITY_NAME
, cityName
);
73 locationValues
.put(LocationEntry
.COLUMN_COORD_LAT
, lat
);
74 locationValues
.put(LocationEntry
.COLUMN_COORD_LONG
, lon
);
76 Uri locationInsertUri
= mContext
.getContentResolver()
77 .insert(LocationEntry
.CONTENT_URI
, locationValues
);
79 return ContentUris
.parseId(locationInsertUri
);
84 * Take the String representing the complete forecast in JSON Format and
85 * pull out the data we need to construct the Strings needed for the wireframes.
87 * Fortunately parsing is easy: constructor takes the JSON string and converts it
88 * into an Object hierarchy for us.
90 private void getWeatherDataFromJson(String forecastJsonStr
, int numDays
,
91 String locationSetting
)
92 throws JSONException
{
94 // These are the names of the JSON objects that need to be extracted.
96 // Location information
97 final String OWM_CITY
= "city";
98 final String OWM_CITY_NAME
= "name";
99 final String OWM_COORD
= "coord";
100 final String OWM_COORD_LAT
= "lat";
101 final String OWM_COORD_LONG
= "lon";
103 // Weather information. Each day's forecast info is an element of the "list" array.
104 final String OWM_LIST
= "list";
106 final String OWM_DATETIME
= "dt";
107 final String OWM_PRESSURE
= "pressure";
108 final String OWM_HUMIDITY
= "humidity";
109 final String OWM_WINDSPEED
= "speed";
110 final String OWM_WIND_DIRECTION
= "deg";
112 // All temperatures are children of the "temp" object.
113 final String OWM_TEMPERATURE
= "temp";
114 final String OWM_MAX
= "max";
115 final String OWM_MIN
= "min";
117 final String OWM_WEATHER
= "weather";
118 final String OWM_DESCRIPTION
= "main";
119 final String OWM_WEATHER_ID
= "id";
121 JSONObject forecastJson
= new JSONObject(forecastJsonStr
);
122 JSONArray weatherArray
= forecastJson
.getJSONArray(OWM_LIST
);
124 JSONObject cityJson
= forecastJson
.getJSONObject(OWM_CITY
);
125 String cityName
= cityJson
.getString(OWM_CITY_NAME
);
126 JSONObject coordJSON
= cityJson
.getJSONObject(OWM_COORD
);
127 double cityLatitude
= coordJSON
.getLong(OWM_COORD_LAT
);
128 double cityLongitude
= coordJSON
.getLong(OWM_COORD_LONG
);
130 // Log.v(LOG_TAG, cityName + ", with coord: " + cityLatitude + " " + cityLongitude);
132 // Insert the location into the database.
133 long locationID
= addLocation(locationSetting
, cityName
, cityLatitude
, cityLongitude
);
135 // Get and insert the new weather information into the database
136 Vector
<ContentValues
> cVVector
= new Vector
<ContentValues
>(weatherArray
.length());
138 // String[] resultStrs = new String[numDays];
139 for (int i
= 0; i
< weatherArray
.length(); i
++) {
140 // These are the values that will be collected.
146 double windDirection
;
154 // Get the JSON object representing the day
155 JSONObject dayForecast
= weatherArray
.getJSONObject(i
);
157 // The date/time is returned as a long. We need to convert that
158 // into something human-readable, since most people won't read "1400356800" as
160 dateTime
= dayForecast
.getLong(OWM_DATETIME
);
162 pressure
= dayForecast
.getDouble(OWM_PRESSURE
);
163 humidity
= dayForecast
.getInt(OWM_HUMIDITY
);
164 windSpeed
= dayForecast
.getDouble(OWM_WINDSPEED
);
165 windDirection
= dayForecast
.getDouble(OWM_WIND_DIRECTION
);
167 // Description is in a child array called "weather", which is 1 element long.
168 // That element also contains a weather code.
169 JSONObject weatherObject
=
170 dayForecast
.getJSONArray(OWM_WEATHER
).getJSONObject(0);
171 description
= weatherObject
.getString(OWM_DESCRIPTION
);
172 weatherId
= weatherObject
.getInt(OWM_WEATHER_ID
);
174 // Temperatures are in a child object called "temp". Try not to name variables
175 // "temp" when working with temperature. It confuses everybody.
176 JSONObject temperatureObject
= dayForecast
.getJSONObject(OWM_TEMPERATURE
);
177 high
= temperatureObject
.getDouble(OWM_MAX
);
178 low
= temperatureObject
.getDouble(OWM_MIN
);
180 ContentValues weatherValues
= new ContentValues();
182 weatherValues
.put(WeatherEntry
.COLUMN_LOC_KEY
, locationID
);
183 weatherValues
.put(WeatherEntry
.COLUMN_DATETEXT
,
184 WeatherContract
.getDbDateString(new Date(dateTime
* 1000L)));
185 weatherValues
.put(WeatherEntry
.COLUMN_HUMIDITY
, humidity
);
186 weatherValues
.put(WeatherEntry
.COLUMN_PRESSURE
, pressure
);
187 weatherValues
.put(WeatherEntry
.COLUMN_WIND_SPEED
, windSpeed
);
188 weatherValues
.put(WeatherEntry
.COLUMN_DEGREES
, windDirection
);
189 weatherValues
.put(WeatherEntry
.COLUMN_MAX_TEMP
, high
);
190 weatherValues
.put(WeatherEntry
.COLUMN_MIN_TEMP
, low
);
191 weatherValues
.put(WeatherEntry
.COLUMN_SHORT_DESC
, description
);
192 weatherValues
.put(WeatherEntry
.COLUMN_WEATHER_ID
, weatherId
);
194 cVVector
.add(weatherValues
);
196 if (cVVector
.size() > 0) {
197 ContentValues
[] cvArray
= new ContentValues
[cVVector
.size()];
198 cVVector
.toArray(cvArray
);
199 mContext
.getContentResolver().bulkInsert(WeatherEntry
.CONTENT_URI
, cvArray
);
204 protected Void
doInBackground(String
... params
) {
206 // If there's no zip code, there's nothing to look up. Verify size of params.
207 if (params
.length
== 0) {
210 String locationQuery
= params
[0];
212 // These two need to be declared outside the try/catch
213 // so that they can be closed in the finally block.
214 HttpURLConnection urlConnection
= null;
215 BufferedReader reader
= null;
217 // Will contain the raw JSON response as a string.
218 String forecastJsonStr
= null;
220 String format
= "json";
221 String units
= "metric";
225 // Construct the URL for the OpenWeatherMap query
226 // Possible parameters are avaiable at OWM's forecast API page, at
227 // http://openweathermap.org/API#forecast
228 final String FORECAST_BASE_URL
=
229 "http://api.openweathermap.org/data/2.5/forecast/daily?";
230 final String QUERY_PARAM
= "q";
231 final String FORMAT_PARAM
= "mode";
232 final String UNITS_PARAM
= "units";
233 final String DAYS_PARAM
= "cnt";
235 Uri builtUri
= Uri
.parse(FORECAST_BASE_URL
).buildUpon()
236 .appendQueryParameter(QUERY_PARAM
, params
[0])
237 .appendQueryParameter(FORMAT_PARAM
, format
)
238 .appendQueryParameter(UNITS_PARAM
, units
)
239 .appendQueryParameter(DAYS_PARAM
, Integer
.toString(numDays
))
242 URL url
= new URL(builtUri
.toString());
244 // Create the request to OpenWeatherMap, and open the connection
245 urlConnection
= (HttpURLConnection
) url
.openConnection();
246 urlConnection
.setRequestMethod("GET");
247 urlConnection
.connect();
249 // Read the input stream into a String
250 InputStream inputStream
= urlConnection
.getInputStream();
251 StringBuffer buffer
= new StringBuffer();
252 if (inputStream
== null) {
256 reader
= new BufferedReader(new InputStreamReader(inputStream
));
259 while ((line
= reader
.readLine()) != null) {
260 // Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
261 // But it does make debugging a *lot* easier if you print out the completed
262 // buffer for debugging.
263 buffer
.append(line
+ "\n");
266 if (buffer
.length() == 0) {
267 // Stream was empty. No point in parsing.
270 forecastJsonStr
= buffer
.toString();
271 } catch (IOException e
) {
272 Log
.e(LOG_TAG
, "Error ", e
);
273 // If the code didn't successfully get the weather data, there's no point in attemping
277 if (urlConnection
!= null) {
278 urlConnection
.disconnect();
280 if (reader
!= null) {
283 } catch (final IOException e
) {
284 Log
.e(LOG_TAG
, "Error closing stream", e
);
290 getWeatherDataFromJson(forecastJsonStr
, numDays
, locationQuery
);
291 } catch (JSONException e
) {
292 Log
.e(LOG_TAG
, e
.getMessage(), e
);
295 // This will only happen if there was an error getting or parsing the forecast.