/*
* Copyright (C) 2014 Francesco Azzola
* Surviving with Android (http://www.survivingwithandroid.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.survivingwithandroid.weather.lib.provider.wunderground;
import android.util.Log;
import com.survivingwithandroid.weather.lib.WeatherCode;
import com.survivingwithandroid.weather.lib.WeatherConfig;
import com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException;
import com.survivingwithandroid.weather.lib.exception.WeatherLibException;
import com.survivingwithandroid.weather.lib.model.City;
import com.survivingwithandroid.weather.lib.model.CurrentWeather;
import com.survivingwithandroid.weather.lib.model.DayForecast;
import com.survivingwithandroid.weather.lib.model.HistoricalHourWeather;
import com.survivingwithandroid.weather.lib.model.HistoricalWeather;
import com.survivingwithandroid.weather.lib.model.HourForecast;
import com.survivingwithandroid.weather.lib.model.Location;
import com.survivingwithandroid.weather.lib.model.Weather;
import com.survivingwithandroid.weather.lib.model.WeatherForecast;
import com.survivingwithandroid.weather.lib.model.WeatherHourForecast;
import com.survivingwithandroid.weather.lib.provider.IWeatherCodeProvider;
import com.survivingwithandroid.weather.lib.provider.IWeatherProvider;
import com.survivingwithandroid.weather.lib.request.Params;
import com.survivingwithandroid.weather.lib.request.WeatherRequest;
import com.survivingwithandroid.weather.lib.util.WeatherUtility;
import com.survivingwithandroid.weather.lib.model.BaseWeather;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
/**
* This is a concrete implementaton of IWeatherProvider interface for Weather undergound provider
*
* @author Francesco Azzola
* */
public class WeatherUndergroundProvider implements IWeatherProvider {
private static String BASE_URL_ID = "http://api.wunderground.com/api";
private static String IMG_URL = "http://icons.wxug.com/i/c/k/";
private static String SEARCH_URL = "http://autocomplete.wunderground.com/aq?query=";
private static String BASE_FORECAST_URL_ID = "http://api.wunderground.com/api";
private WeatherConfig config;
private BaseWeather.WeatherUnit units = new BaseWeather.WeatherUnit();
private IWeatherCodeProvider codeProvider;
private WeatherForecast forecast = new WeatherForecast();
public CurrentWeather getCurrentCondition(String data) throws WeatherLibException {
//Log.d("SwA", "JSON CurrentWeather [" + data + "]");
CurrentWeather cWeather = new CurrentWeather();
Weather weather = new Weather();
try {
// We create out JSONObject from the data
JSONObject rootObj = new JSONObject(data);
JSONObject jObj = getObject("current_observation", rootObj);
// We start extracting the info
JSONObject dObj = getObject("display_location", jObj);
Location loc = new Location();
loc.setLatitude(getFloat("latitude", dObj));
loc.setLongitude(getFloat("longitude", dObj));
loc.setCountry(getString("state_name", dObj));
loc.setCity(getString("city", dObj));
weather.location = loc;
// Convert internal code
weather.currentCondition.setDescr(getString("weather", jObj));
//weather.currentCondition.setCondition(getString("main", JSONWeather));
weather.currentCondition.setIcon(getString("icon", jObj));
if (codeProvider != null) {
try {
weather.currentCondition.setWeatherCode(codeProvider.getWeatherCode(weather.currentCondition.getIcon()));
} catch (Throwable t) {
weather.currentCondition.setWeatherCode(WeatherCode.NOT_AVAILABLE);
}
}
//JSONObject mainObj = getObject("main", jObj);
String relUm = getString("relative_humidity", jObj);
weather.currentCondition.setHumidity(Integer.parseInt(relUm.substring(0, relUm.length() - 1)));
weather.wind.setDeg(getFloat("wind_degrees", jObj));
String trend = getString("pressure_trend", jObj);
int trendVal = -1;
if ("-".equals(trend))
trendVal = 0;
else
trendVal = Integer.parseInt(trend);
weather.currentCondition.setPressureTrend(trendVal);
weather.currentCondition.setUV(getFloat("UV", jObj));
weather.currentCondition.setSolarRadiation(getString("solarradiation", jObj));
if (WeatherUtility.isMetric(config.unitSystem)) {
weather.currentCondition.setPressure(getInt("pressure_mb", jObj));
weather.temperature.setTemp(getFloat("temp_c", jObj));
// Wind
weather.wind.setGust(getFloat("wind_gust_kph", jObj));
weather.wind.setSpeed(getFloat("wind_kph", jObj));
weather.currentCondition.setVisibility(getFloat("visibility_km", jObj));
weather.currentCondition.setFeelsLike(getFloat("feelslike_c", jObj));
weather.currentCondition.setDewPoint(getFloat("dewpoint_c", jObj));
weather.currentCondition.setHeatIndex(getString("heat_index_c", jObj));
} else {
weather.currentCondition.setPressure(getInt("pressure_in", jObj));
// weather.temperature.setMaxTemp(getFloat("temp_max", mainObj));
// weather.temperature.setMinTemp(getFloat("temp_min", mainObj));
weather.temperature.setTemp(getFloat("temp_f", jObj));
// Wind
weather.wind.setGust(getFloat("wind_gust_mph", jObj));
weather.wind.setSpeed(getFloat("wind_mph", jObj));
weather.currentCondition.setVisibility(getFloat("visibility_mi", jObj));
weather.currentCondition.setFeelsLike(getFloat("feelslike_f", jObj));
weather.currentCondition.setDewPoint(getFloat("dewpoint_f", jObj));
weather.currentCondition.setHeatIndex(getString("heat_index_f", jObj));
}
parseForecast(rootObj, weather);
// Astronomy
JSONObject moonObj = getObject("moon_phase", rootObj);
weather.location.getAstronomy().percIllum = getString("percentIlluminated", moonObj);
weather.location.getAstronomy().moonAge = getString("ageOfMoon", moonObj);
weather.location.getAstronomy().moonPhaseDescr = getString("phaseofMoon", moonObj);
weather.location.getAstronomy().hemisphere = getString("hemisphere", moonObj);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
JSONObject riseObj = getObject("sunrise", moonObj);
String d1 = getString("hour", riseObj) + ":" + getString("minute", riseObj);
try {
weather.location.setSunrise(sdf.parse(d1).getTime());
} catch (ParseException e) {
//e.printStackTrace();
}
JSONObject setObj = getObject("sunset", moonObj);
String d2 = getString("hour", setObj) + ":" + getString("minute", setObj);
try {
weather.location.setSunset(sdf.parse(d2).getTime());
} catch (ParseException e) {
// e.printStackTrace();
}
} catch (JSONException json) {
//json.printStackTrace();
throw new WeatherLibException(json);
}
cWeather.setUnit(units);
cWeather.weather = weather;
return cWeather;
}
public WeatherForecast getForecastWeather(String data) throws WeatherLibException {
try {
JSONObject rootObj = new JSONObject(data);
parseForecast(rootObj, null);
} catch (JSONException json) {
json.printStackTrace();
throw new WeatherLibException(json);
}
return forecast;
}
private void parseForecast(JSONObject root, Weather weather) throws JSONException {
// Start parsing forecast
JSONObject forecast1 = getObject("forecast", root);
JSONObject simpleForecast = getObject("simpleforecast", forecast1);
JSONArray jArr = simpleForecast.getJSONArray("forecastday");
for (int i = 0; i < jArr.length(); i++) {
JSONObject dayForecast = jArr.getJSONObject(i);
DayForecast df = new DayForecast();
JSONObject jsonDate = dayForecast.getJSONObject("date");
df.timestamp = jsonDate.getLong("epoch");
df.weather.currentCondition.setDescr(dayForecast.getString("conditions"));
df.weather.currentCondition.setIcon(dayForecast.getString("icon"));
if (codeProvider != null) {
try {
df.weather.currentCondition.setWeatherCode(codeProvider.getWeatherCode(df.weather.currentCondition.getIcon()));
} catch (Throwable t) {
df.weather.currentCondition.setWeatherCode(WeatherCode.NOT_AVAILABLE);
}
}
if (WeatherUtility.isMetric(config.unitSystem)) {
df.forecastTemp.max = dayForecast.getJSONObject("high").getInt("celsius");
df.forecastTemp.min = dayForecast.getJSONObject("low").getInt("celsius");
df.weather.wind.setSpeed(dayForecast.getJSONObject("avewind").getInt("kph"));
df.weather.snow.setTime("Day");
df.weather.snow.setAmmount(dayForecast.getJSONObject("snow_allday").getInt("cm"));
df.weather.rain[0].setTime("Day");
df.weather.rain[0].setAmmount(dayForecast.getJSONObject("qpf_allday").getInt("mm"));
} else {
df.forecastTemp.max = dayForecast.getJSONObject("high").getInt("fahrenheit");
df.forecastTemp.min = dayForecast.getJSONObject("low").getInt("fahrenheit");
df.weather.wind.setSpeed(dayForecast.getJSONObject("avewind").getInt("mph"));
df.weather.snow.setTime("Day");
df.weather.snow.setAmmount(dayForecast.getJSONObject("snow_allday").getInt("in"));
df.weather.rain[0].setTime("Day");
df.weather.rain[0].setAmmount(dayForecast.getJSONObject("qpf_allday").getInt("in"));
}
df.weather.wind.setDeg(dayForecast.getJSONObject("avewind").getInt("degrees"));
if (i == 0 && weather != null) {
weather.temperature.setMinTemp(df.forecastTemp.min);
weather.temperature.setMaxTemp(df.forecastTemp.max);
}
forecast.addForecast(df);
}
forecast.setUnit(units);
}
public List<City> getCityResultList(String data) throws WeatherLibException {
List<City> cityList = new ArrayList<City>();
// Log.d("SwA", "Data ["+data+"]");
try {
JSONObject jObj = new JSONObject(data);
JSONArray jArr = jObj.getJSONArray("RESULTS");
for (int i = 0; i < jArr.length(); i++) {
JSONObject obj = jArr.getJSONObject(i);
String name = obj.getString("name");
String id = obj.getString("l");
String country = obj.getString("c");
//Log.d("SwA", "ID [" + id + "]");
City.CityBuilder builder = new City.CityBuilder().name(name).id(id).country(country);
// City c = new City(id, name, null, country);
City c = builder.build();
cityList.add(c);
}
} catch (JSONException json) {
throw new WeatherLibException(json);
}
return cityList;
}
@Override
public WeatherHourForecast getHourForecastWeather(String data) throws WeatherLibException {
WeatherHourForecast forecast = new WeatherHourForecast();
try {
JSONObject jObj = new JSONObject(data);
JSONArray jHoursArray = jObj.getJSONArray("hourly_forecast");
for (int i = 0; i < jHoursArray.length(); i++) {
JSONObject jHour = jHoursArray.getJSONObject(i);
HourForecast hourForecast = new HourForecast();
hourForecast.timestamp = jHour.getJSONObject("FCTTIME").getLong("epoch");
JSONObject jTemp = jHour.getJSONObject("temp");
JSONObject jDewPoint = jHour.getJSONObject("dewpoint");
JSONObject jWindSpeed = jHour.getJSONObject("wspd");
JSONObject jWindDir = jHour.getJSONObject("wdir");
JSONObject jHeatIdx = jHour.getJSONObject("heatindex");
JSONObject jFeelslike = jHour.getJSONObject("feelslike");
JSONObject jQPF = jHour.getJSONObject("qpf");
JSONObject jSnow = jHour.getJSONObject("snow");
hourForecast.weather.currentCondition.setDescr(jHour.getString("conditions"));
hourForecast.weather.currentCondition.setIcon(jHour.getString("icon"));
hourForecast.weather.currentCondition.setHumidity(getFloat("humidity", jHour));
hourForecast.weather.currentCondition.setUV(getFloat("uvi", jHour));
hourForecast.weather.wind.setDeg(getFloat("degrees", jWindDir));
String tag = null;
if (WeatherUtility.isMetric(config.unitSystem))
tag = "metric";
else
tag = "english";
hourForecast.weather.temperature.setTemp(getFloat(tag, jTemp));
hourForecast.weather.currentCondition.setDewPoint(getFloat(tag, jDewPoint));
hourForecast.weather.wind.setSpeed(getFloat(tag, jWindSpeed));
hourForecast.weather.currentCondition.setFeelsLike(getFloat(tag, jFeelslike));
hourForecast.weather.currentCondition.setHeatIndex(getString(tag, jHeatIdx));
hourForecast.weather.rain[0].setAmmount(getFloat(tag, jQPF));
hourForecast.weather.snow.setAmmount(getFloat(tag, jSnow));
forecast.addForecast(hourForecast);
}
} catch (JSONException json) {
throw new WeatherLibException(json);
}
return forecast;
}
@Override
public HistoricalWeather getHistoricalWeather(String data) throws WeatherLibException {
HistoricalWeather histWeather = new HistoricalWeather();
try {
JSONObject jObj = new JSONObject(data);
JSONObject histObj = jObj.getJSONObject("history");
// We move to the list tag
JSONArray wList = histObj.getJSONArray("observations");
for (int i=0; i < wList.length(); i++) {
HistoricalHourWeather hhWeather = new HistoricalHourWeather();
JSONObject jHour = wList.getJSONObject(i);
JSONObject utcObj = jHour.getJSONObject("utcdate");
int y = utcObj.getInt("year");
int m = utcObj.getInt("mon");
int mday = utcObj.getInt("mday");
int h = utcObj.getInt("hour");
int min = utcObj.getInt("min");
Calendar cal = GregorianCalendar.getInstance();
cal.set(y,Calendar.JANUARY,mday,h,min);
cal.add(Calendar.MONTH, m - 1);
hhWeather.timestamp = cal.getTimeInMillis();
String tag = null;
if (WeatherUtility.isMetric(config.unitSystem))
tag = "m";
else
tag = "i";
hhWeather.weather.temperature.setTemp((float) jHour.getDouble("temp" + tag));
hhWeather.weather.currentCondition.setDewPoint((float) jHour.getDouble("dewpt" + tag));
hhWeather.weather.currentCondition.setHumidity((float) jHour.getInt("hum"));
hhWeather.weather.wind.setSpeed((float) jHour.getDouble("wspd" + tag));
hhWeather.weather.wind.setGust((float) jHour.getDouble("wgust" + tag));
hhWeather.weather.wind.setDeg((float) jHour.getDouble("wdird"));
hhWeather.weather.wind.setChill((float) jHour.getDouble("windchill" + tag));
hhWeather.weather.currentCondition.setVisibility((float) jHour.getDouble("vis" + tag));
hhWeather.weather.currentCondition.setPressure((float) jHour.getDouble("pressure" + tag));
hhWeather.weather.currentCondition.setHeatIndex(jHour.getString("heatindex" + tag));
hhWeather.weather.rain[0].setAmmount((float) jHour.getDouble("precip" + tag));
hhWeather.weather.currentCondition.setDescr(jHour.getString("conds"));
hhWeather.weather.currentCondition.setIcon(jHour.getString("icon"));
if (codeProvider != null) {
try {
hhWeather.weather.currentCondition.setWeatherCode(codeProvider.getWeatherCode(hhWeather.weather.currentCondition.getIcon()));
} catch (Throwable t) {
hhWeather.weather.currentCondition.setWeatherCode(WeatherCode.NOT_AVAILABLE);
}
}
// fog, hail, tornado and so on still not supported
histWeather.addHistoricalHourWeather(hhWeather);
}
}
catch(JSONException json) {
throw new WeatherLibException(json);
}
histWeather.setUnit(units);
return histWeather;
}
@Override
public void setConfig(WeatherConfig config) {
this.config = config;
units = WeatherUtility.createWeatherUnit(config.unitSystem);
}
@Override
public String getQueryCityURL(String cityNamePattern) {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
cityNamePattern = cityNamePattern.replaceAll(" ", "%20");
return SEARCH_URL + cityNamePattern; // + "&cnt=" + config.maxResult;
}
/*
@Override
public String getQueryCurrentWeatherURL(String cityId) {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
String url = BASE_URL_ID + "/" + config.ApiKey + "/forecast/conditions/astronomy/";
url = addLanguage(url);
url = url + cityId + ".json";
return url;
}
*/
/*
@Override
public String getQueryForecastWeatherURL(String cityId) {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
String url = BASE_FORECAST_URL_ID + "/" + config.ApiKey + "/forecast/";
url = addLanguage(url);
return url + cityId + ".json";
}
*/
@Override
public String getQueryImageURL(String icon) throws ApiKeyRequiredException {
return IMG_URL + icon + ".gif";
}
/*
@Override
public String getQueryHourForecastWeatherURL(String cityId) throws ApiKeyRequiredException {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
String url = BASE_FORECAST_URL_ID + "/" + config.ApiKey + "/hourly/";
url = addLanguage(url);
return url + cityId + ".json";
}
*/
@Override
public void setWeatherCodeProvider(IWeatherCodeProvider codeProvider) {
this.codeProvider = codeProvider;
}
@Override
public String getQueryCityURLByLocation(android.location.Location location) throws ApiKeyRequiredException {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
return BASE_URL_ID + "/" + config.ApiKey + "/geolookup/q/" + location.getLatitude() + "," + location.getLongitude() + ".json";
}
@Override
public String getQueryCityURLByCoord(double lon, double lat) throws ApiKeyRequiredException {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
return BASE_URL_ID + "/" + config.ApiKey + "/geolookup/q/" + lat + "," + lon + ".json";
}
/*
@Override
public String getQueryHistoricalWeatherURL(String cityId, Date startDate, Date endDate) throws ApiKeyRequiredException {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String url = BASE_URL_ID + "/" + config.ApiKey + "/history_" + sdf.format(startDate);
url = addLanguage(url);
return url + cityId + ".json";
}
*/
@Override
public String getQueryLayerURL(String cityId, Params params) throws ApiKeyRequiredException {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
String url = BASE_URL_ID + "/" + config.ApiKey + "/" + params.getImageType() + "/" +
( (cityId == null || cityId.equals("")) ? "image.png" : cityId + ".png") +
"?" + params.string();
return url;
}
private JSONObject getObject(String tagName, JSONObject jObj) throws JSONException {
JSONObject subObj = jObj.getJSONObject(tagName);
return subObj;
}
private String getString(String tagName, JSONObject jObj) throws JSONException {
return jObj.getString(tagName);
}
private float getFloat(String tagName, JSONObject jObj) throws JSONException {
try {
return (float) jObj.getDouble(tagName);
} catch (Throwable t) {
return -1;
}
}
private int getInt(String tagName, JSONObject jObj) throws JSONException {
return jObj.getInt(tagName);
}
private String addLanguage(String url) {
if (config.lang == null)
return url;
String nUrl = url + "/lang:" + config.lang.toUpperCase() + "/";
return nUrl;
}
// New methods
@Override
public String getQueryCurrentWeatherURL(WeatherRequest request) throws ApiKeyRequiredException {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
String url = BASE_URL_ID + "/" + config.ApiKey + "/forecast/conditions/astronomy/";
url = addLanguage(url);
if (request.getCityId() != null)
url = url + request.getCityId() + ".json";
else
url = url + request.getLat() + "," + request.getLon() + ".json";
return url;
}
@Override
public String getQueryForecastWeatherURL(WeatherRequest request) throws ApiKeyRequiredException {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
String url = BASE_FORECAST_URL_ID + "/" + config.ApiKey + "/forecast/";
url = addLanguage(url);
if (request.getCityId() != null)
url = url + request.getCityId() + ".json";
else
url = url + request.getLat() + "," + request.getLon() + ".json";
return url;
}
@Override
public String getQueryHourForecastWeatherURL(WeatherRequest request) throws ApiKeyRequiredException {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
String url = BASE_FORECAST_URL_ID + "/" + config.ApiKey + "/hourly/";
url = addLanguage(url);
if (request.getCityId() != null)
url = url + request.getCityId() + ".json";
else
url = url + request.getLat() + "," + request.getLon() + ".json";
return url;
}
@Override
public String getQueryHistoricalWeatherURL(WeatherRequest request, Date startDate, Date endDate) throws ApiKeyRequiredException {
if (config.ApiKey == null)
throw new ApiKeyRequiredException();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String url = BASE_URL_ID + "/" + config.ApiKey + "/history_" + sdf.format(startDate);
url = addLanguage(url);
if (request.getCityId() != null)
url = url + request.getCityId() + ".json";
else
url = url + request.getLat() + "," + request.getLon() + ".json";
return url;
}
}