package net.maxbraun.mirror;
import android.content.Context;
import android.location.Location;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import net.maxbraun.mirror.Weather.WeatherData;
/**
* A helper class to regularly retrieve weather information.
*/
public class Weather extends DataUpdater<WeatherData> {
private static final String TAG = Weather.class.getSimpleName();
/**
* The time in milliseconds between API calls to update the weather.
*/
private static final long UPDATE_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(5);
/**
* The context used to load string resources.
*/
private final Context context;
/**
* A {@link Map} from Dark Sky's icon code to the corresponding drawable resource ID.
*/
private final Map<String, Integer> iconResources = new HashMap<String, Integer>() {{
put("clear-day", R.drawable.clear_day);
put("clear-night", R.drawable.clear_night);
put("cloudy", R.drawable.cloudy);
put("fog", R.drawable.fog);
put("partly-cloudy-day", R.drawable.partly_cloudy_day);
put("partly-cloudy-night", R.drawable.partly_cloudy_night);
put("rain", R.drawable.rain);
put("sleet", R.drawable.sleet);
put("snow", R.drawable.snow);
put("wind", R.drawable.wind);
}};
/**
* The current location, which is assumed to be static.
*/
private Location location;
/**
* The data structure containing the weather information we are interested in.
*/
public class WeatherData {
/**
* The current temperature in degrees Fahrenheit.
*/
public final double currentTemperature;
/**
* The current precipitation probability as a value between 0 and 1.
*/
public final double currentPrecipitationProbability;
/**
* A human-readable summary of the 24-hour forecast.
*/
public final String daySummary;
/**
* The average precipitation probability during the 24-hour forecast as a value between 0 and 1.
*/
public final double dayPrecipitationProbability;
/**
* The resource ID of the icon representing the current weather conditions.
*/
public final int currentIcon;
/**
* The resource ID of the icon representing the weather conditions during the 24-hour forecast.
*/
public final int dayIcon;
public WeatherData(double currentTemperature, double currentPrecipitationProbability,
String daySummary, double dayPrecipitationProbability, int currentIcon, int dayIcon) {
this.currentTemperature = currentTemperature;
this.currentPrecipitationProbability = currentPrecipitationProbability;
this.daySummary = daySummary;
this.dayPrecipitationProbability = dayPrecipitationProbability;
this.currentIcon = currentIcon;
this.dayIcon = dayIcon;
}
}
public Weather(Context context, UpdateListener<WeatherData> updateListener) {
super(updateListener, UPDATE_INTERVAL_MILLIS);
this.context = context;
}
@Override
protected WeatherData getData() {
// Lazy load the location.
if (location == null) {
// We're using geo location by IP, because many headless Android devices don't return anything
// useful through the usual location APIs.
location = GeoLocation.getLocation();
Log.d(TAG, "Using location for weather: " + location);
}
// Get the latest data from the Dark Sky API.
String requestUrl = getRequestUrl(location);
// Parse the data we are interested in from the response JSON.
try {
JSONObject response = Network.getJson(requestUrl);
if (response != null) {
return new WeatherData(
parseCurrentTemperature(response),
parseCurrentPrecipitationProbability(response),
parseDaySummary(response),
parseDayPrecipitationProbability(response),
parseCurrentIcon(response),
parseDayIcon(response));
} else {
return null;
}
} catch (JSONException e) {
Log.e(TAG, "Failed to parse weather JSON.", e);
return null;
}
}
/**
* Creates the URL for a Dark Sky API request based on the specified {@link Location} or
* {@code null} if the location is unknown.
*/
private String getRequestUrl(Location location) {
if (location != null) {
return String.format(Locale.US, "https://api.darksky.net/forecast/%s/%f,%f",
context.getString(R.string.dark_sky_api_key),
location.getLatitude(),
location.getLongitude());
} else {
return null;
}
}
/**
* Reads the current temperature from the API response. API documentation:
* https://darksky.net/dev/docs
*/
private Double parseCurrentTemperature(JSONObject response) throws JSONException {
JSONObject currently = response.getJSONObject("currently");
return currently.getDouble("temperature");
}
/**
* Reads the current precipitation probability from the API response. API documentation:
* https://darksky.net/dev/docs
*/
private Double parseCurrentPrecipitationProbability(JSONObject response) throws JSONException {
JSONObject currently = response.getJSONObject("currently");
return currently.getDouble("precipProbability");
}
/**
* Reads the 24-hour forecast summary from the API response. API documentation:
* https://darksky.net/dev/docs
*/
private String parseDaySummary(JSONObject response) throws JSONException {
JSONObject hourly = response.getJSONObject("hourly");
return hourly.getString("summary");
}
/**
* Reads the 24-hour forecast precipitation probability from the API response. API documentation:
* https://darksky.net/dev/docs
*/
private Double parseDayPrecipitationProbability(JSONObject response) throws JSONException {
JSONObject hourly = response.getJSONObject("hourly");
JSONArray data = hourly.getJSONArray("data");
// Calculate the average over the whole day.
double sum = 0;
for (int i = 0; i < data.length(); i++) {
double probability = data.getJSONObject(i).getDouble("precipProbability");
sum += probability;
}
return sum / data.length();
}
/**
* Reads the current weather icon code from the API response. API documentation:
* https://darksky.net/dev/docs
*/
private Integer parseCurrentIcon(JSONObject response) throws JSONException {
JSONObject currently = response.getJSONObject("currently");
String icon = currently.getString("icon");
return iconResources.get(icon);
}
/**
* Reads the 24-hour forecast weather icon code from the API response. API documentation:
* https://darksky.net/dev/docs
*/
private Integer parseDayIcon(JSONObject response) throws JSONException {
JSONObject hourly = response.getJSONObject("hourly");
String icon = hourly.getString("icon");
return iconResources.get(icon);
}
@Override
protected String getTag() {
return TAG;
}
}