Skip to content

Instantly share code, notes, and snippets.

@SemonCat
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SemonCat/48e60bb5523243ba0c80 to your computer and use it in GitHub Desktop.
Save SemonCat/48e60bb5523243ba0c80 to your computer and use it in GitHub Desktop.
YahooWeather
package com.asus.feedbacksdk.util;
import java.io.IOException;
import java.io.StringReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import android.content.Context;
import android.content.res.Resources;
import android.location.Location;
import android.net.Uri;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
public class YahooWeather {
private static final String TAG = "YahooWeatherProvider";
private static final String URL_WEATHER = "http://weather.yahooapis.com/forecastrss?w=%s&u=%s";
private static final String URL_LOCATION = "http://query.yahooapis.com/v1/public/yql?format=json&q=" + Uri.encode("select woeid, postal, admin1, admin2, admin3, " + "locality1, locality2, country from geo.places where " + "(placetype = 7 or placetype = 8 or placetype = 9 " + "or placetype = 10 or placetype = 11 or placetype = 20) and text =");
private static final String URL_PLACEFINDER = "http://query.yahooapis.com/v1/public/yql?format=json&q=" + Uri.encode("select woeid, city, neighborhood, county from geo.placefinder where " + "gflags=\"R\" and text =");
private static final String[] LOCALITY_NAMES = new String[] { "locality1", "locality2", "admin3", "admin2", "admin1" };
private static final String[] PLACE_NAMES = new String[] { "city", "neigborhood", "county" };
private Context mContext;
public class LocationResult {
public String id;
public String city;
public String postal;
public String countryId;
public String country;
}
public static class WeatherInfo {
private static final DecimalFormat sNoDigitsFormat = new DecimalFormat("0");
private Context mContext;
private String id;
private String city;
private String condition;
private int conditionCode;
private float temperature;
private String tempUnit;
private float humidity;
private float wind;
private int windDirection;
private String speedUnit;
private long timestamp;
private ArrayList<DayForecast> forecasts;
public WeatherInfo(Context context, String id, String city, String condition, int conditionCode, float temp, String tempUnit, float humidity, float wind, int windDir, String speedUnit, ArrayList<DayForecast> forecasts, long timestamp) {
this.mContext = context.getApplicationContext();
this.id = id;
this.city = city;
this.condition = condition;
this.conditionCode = conditionCode;
this.humidity = humidity;
this.wind = wind;
this.windDirection = windDir;
this.speedUnit = speedUnit;
this.timestamp = timestamp;
this.temperature = temp;
this.tempUnit = tempUnit;
this.forecasts = forecasts;
}
public static class DayForecast {
public final float low, high;
public final int conditionCode;
public final String condition;
public DayForecast(float low, float high, String condition, int conditionCode) {
this.low = low;
this.high = high;
this.condition = condition;
this.conditionCode = conditionCode;
}
public String getFormattedLow() {
return getFormattedValue(low, "\u00b0");
}
public String getFormattedHigh() {
return getFormattedValue(high, "\u00b0");
}
public String getCondition(Context context) {
return WeatherInfo.getCondition(context, conditionCode, condition);
}
}
public String getId() {
return id;
}
public String getCity() {
return city;
}
public String getCondition() {
return getCondition(mContext, conditionCode, condition);
}
private static String getCondition(Context context, int conditionCode, String condition) {
final Resources res = context.getResources();
final int resId = res.getIdentifier("weather_" + conditionCode, "string", context.getPackageName());
if (resId != 0) {
return res.getString(resId);
}
return condition;
}
public Date getTimestamp() {
return new Date(timestamp);
}
private static String getFormattedValue(float value, String unit) {
if (Float.isNaN(value)) {
return "-";
}
String formatted = sNoDigitsFormat.format(value);
if (formatted.equals("-0")) {
formatted = "0";
}
return formatted + unit;
}
public String getFormattedTemperature() {
return getFormattedValue(temperature, "\u00b0" + tempUnit);
}
public String getFormattedLow() {
return forecasts.get(0).getFormattedLow();
}
public String getFormattedHigh() {
return forecasts.get(0).getFormattedHigh();
}
public String getFormattedHumidity() {
return getFormattedValue(humidity, "%");
}
public String getFormattedWindSpeed() {
if (wind < 0) {
return "Unknown";
}
return getFormattedValue(wind, speedUnit);
}
public String getWindDirection() {
String resId;
if (windDirection < 0)
return "Unknown";
else if (windDirection < 23)
return "N";
else if (windDirection < 68)
return "NE";
else if (windDirection < 113)
return "E";
else if (windDirection < 158)
return "SE";
else if (windDirection < 203)
return "S";
else if (windDirection < 248)
return "SW";
else if (windDirection < 293)
return "W";
else if (windDirection < 338)
return "NW";
else
return "N";
}
public ArrayList<DayForecast> getForecasts() {
return forecasts;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("WeatherInfo for ");
builder.append(city);
builder.append(" (");
builder.append(id);
builder.append(") @ ");
builder.append(getTimestamp());
builder.append(": ");
builder.append(getCondition());
builder.append("(");
builder.append(conditionCode);
builder.append("), temperature ");
builder.append(getFormattedTemperature());
builder.append(", low ");
builder.append(getFormattedLow());
builder.append(", high ");
builder.append(getFormattedHigh());
builder.append(", humidity ");
builder.append(getFormattedHumidity());
builder.append(", wind ");
builder.append(getFormattedWindSpeed());
builder.append(" at ");
builder.append(getWindDirection());
if (forecasts.size() > 0) {
builder.append(", forecasts:");
}
for (int i = 0; i < forecasts.size(); i++) {
DayForecast d = forecasts.get(i);
if (i != 0) {
builder.append(";");
}
builder.append(" day ").append(i + 1).append(": ");
builder.append("high ").append(d.getFormattedHigh());
builder.append(", low ").append(d.getFormattedLow());
builder.append(", ").append(d.condition);
builder.append("(").append(d.conditionCode).append(")");
}
return builder.toString();
}
public String toSerializedString() {
StringBuilder builder = new StringBuilder();
builder.append(id).append('|');
builder.append(city).append('|');
builder.append(condition).append('|');
builder.append(conditionCode).append('|');
builder.append(temperature).append('|');
builder.append(tempUnit).append('|');
builder.append(humidity).append('|');
builder.append(wind).append('|');
builder.append(windDirection).append('|');
builder.append(speedUnit).append('|');
builder.append(timestamp).append('|');
serializeForecasts(builder);
return builder.toString();
}
private void serializeForecasts(StringBuilder builder) {
builder.append(forecasts.size());
for (DayForecast d : forecasts) {
builder.append(';');
builder.append(d.high).append(';');
builder.append(d.low).append(';');
builder.append(d.condition).append(';');
builder.append(d.conditionCode);
}
}
public static WeatherInfo fromSerializedString(Context context, String input) {
if (input == null) {
return null;
}
String[] parts = input.split("\\|");
if (parts == null || parts.length != 12) {
return null;
}
int conditionCode, windDirection;
long timestamp;
float temperature, humidity, wind;
String[] forecastParts = parts[11].split(";");
int forecastItems;
ArrayList<DayForecast> forecasts = new ArrayList<DayForecast>();
// Parse the core data
try {
conditionCode = Integer.parseInt(parts[3]);
temperature = Float.parseFloat(parts[4]);
humidity = Float.parseFloat(parts[6]);
wind = Float.parseFloat(parts[7]);
windDirection = Integer.parseInt(parts[8]);
timestamp = Long.parseLong(parts[10]);
forecastItems = forecastParts == null ? 0 : Integer.parseInt(forecastParts[0]);
} catch (NumberFormatException e) {
return null;
}
if (forecastItems == 0 || forecastParts.length != 4 * forecastItems + 1) {
return null;
}
// Parse the forecast data
try {
for (int item = 0; item < forecastItems; item++) {
int offset = item * 4 + 1;
DayForecast day = new DayForecast(
/* low */Float.parseFloat(forecastParts[offset + 1]),
/* high */Float.parseFloat(forecastParts[offset]),
/* condition */forecastParts[offset + 2],
/* conditionCode */Integer.parseInt(forecastParts[offset + 3]));
if (!Float.isNaN(day.low) && !Float.isNaN(day.high) && day.conditionCode >= 0) {
forecasts.add(day);
}
}
} catch (NumberFormatException ignored) {
}
if (forecasts.isEmpty()) {
return null;
}
return new WeatherInfo(context,
/* id */parts[0], /* city */parts[1], /* condition */parts[2], conditionCode, temperature, /* tempUnit */parts[5], humidity, wind, windDirection, /* speedUnit */parts[9],
/* forecasts */forecasts, timestamp);
}
}
public YahooWeather(Context context) {
mContext = context;
}
public List<LocationResult> getLocations(String input) {
String language = getLanguage();
String params = "\"" + input + "\" and lang = \"" + language + "\"";
String url = URL_LOCATION + Uri.encode(params);
JSONObject jsonResults = fetchResults(url);
if (jsonResults == null) {
return null;
}
try {
JSONArray places = jsonResults.optJSONArray("place");
if (places == null) {
// Yahoo returns an object instead of an array when there's only
// one result
places = new JSONArray();
places.put(jsonResults.getJSONObject("place"));
}
ArrayList<LocationResult> results = new ArrayList<LocationResult>();
for (int i = 0; i < places.length(); i++) {
LocationResult result = parsePlace(places.getJSONObject(i));
if (result != null) {
results.add(result);
}
}
return results;
} catch (JSONException e) {
Log.e(TAG, "Received malformed places data (input=" + input + ", lang=" + language + ")", e);
}
return null;
}
public WeatherInfo getWeatherInfo(String id, String localizedCityName, boolean metric) {
String url = String.format(URL_WEATHER, id, metric ? "c" : "f");
String response = HttpRetriever.retrieve(url);
if (response == null) {
return null;
}
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
StringReader reader = new StringReader(response);
WeatherHandler handler = new WeatherHandler();
parser.parse(new InputSource(reader), handler);
if (handler.isComplete()) {
// There are cases where the current condition is unknown, but
// the forecast
// is not - using the (inaccurate) forecast is probably better
// than showing
// the question mark
if (handler.conditionCode == 3200) {
handler.condition = handler.forecasts.get(0).condition;
handler.conditionCode = handler.forecasts.get(0).conditionCode;
}
WeatherInfo w = new WeatherInfo(mContext, id, localizedCityName != null ? localizedCityName : handler.city, handler.condition, handler.conditionCode, handler.temperature, handler.temperatureUnit, handler.humidity, handler.windSpeed, handler.windDirection, handler.speedUnit, handler.forecasts, System.currentTimeMillis());
Log.d(TAG, "Weather updated: " + w);
return w;
} else {
Log.w(TAG, "Received incomplete weather XML (id=" + id + ")");
}
} catch (ParserConfigurationException e) {
Log.e(TAG, "Could not create XML parser", e);
} catch (SAXException e) {
Log.e(TAG, "Could not parse weather XML (id=" + id + ")", e);
} catch (IOException e) {
Log.e(TAG, "Could not parse weather XML (id=" + id + ")", e);
}
return null;
}
private static class WeatherHandler extends DefaultHandler {
String city;
String temperatureUnit, speedUnit;
int windDirection, conditionCode;
float humidity, temperature, windSpeed;
String condition;
ArrayList<WeatherInfo.DayForecast> forecasts = new ArrayList<WeatherInfo.DayForecast>();
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equals("yweather:location")) {
city = attributes.getValue("city");
} else if (qName.equals("yweather:units")) {
temperatureUnit = attributes.getValue("temperature");
speedUnit = attributes.getValue("speed");
} else if (qName.equals("yweather:wind")) {
windDirection = (int) stringToFloat(attributes.getValue("direction"), -1);
windSpeed = stringToFloat(attributes.getValue("speed"), -1);
} else if (qName.equals("yweather:atmosphere")) {
humidity = stringToFloat(attributes.getValue("humidity"), -1);
} else if (qName.equals("yweather:condition")) {
condition = attributes.getValue("text");
conditionCode = (int) stringToFloat(attributes.getValue("code"), -1);
temperature = stringToFloat(attributes.getValue("temp"), Float.NaN);
} else if (qName.equals("yweather:forecast")) {
WeatherInfo.DayForecast day = new WeatherInfo.DayForecast(
/* low */stringToFloat(attributes.getValue("low"), Float.NaN),
/* high */stringToFloat(attributes.getValue("high"), Float.NaN),
/* condition */attributes.getValue("text"),
/* conditionCode */(int) stringToFloat(attributes.getValue("code"), -1));
if (!Float.isNaN(day.low) && !Float.isNaN(day.high) && day.conditionCode >= 0) {
forecasts.add(day);
}
}
}
public boolean isComplete() {
return temperatureUnit != null && speedUnit != null && conditionCode >= 0 && !Float.isNaN(temperature) && !forecasts.isEmpty();
}
private float stringToFloat(String value, float defaultValue) {
try {
if (value != null) {
return Float.parseFloat(value);
}
} catch (NumberFormatException e) {
// fall through to the return line below
}
return defaultValue;
}
}
public WeatherInfo getWeatherInfo(Location location, boolean metric) {
String language = getLanguage();
String params = String.format(Locale.US, "\"%f %f\" and locale=\"%s\"", location.getLatitude(), location.getLongitude(), language);
String url = URL_PLACEFINDER + Uri.encode(params);
JSONObject results = fetchResults(url);
if (results == null) {
return null;
}
try {
JSONObject result = results.getJSONObject("Result");
String woeid = result.getString("woeid");
String city = null;
for (String name : PLACE_NAMES) {
if (!result.isNull(name)) {
city = result.getString(name);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, String.format(Locale.US, "Placefinder for location %f %f " + "matched %s using %s", location.getLatitude(), location.getLongitude(), city, name));
}
break;
}
}
// The city name in the placefinder result is HTML encoded :-(
if (city != null) {
city = Html.fromHtml(city).toString();
} else {
Log.w(TAG, "Can not resolve place name for " + location);
}
Log.d(TAG, "Resolved location " + location + " to " + city + " (" + woeid + ")");
WeatherInfo info = getWeatherInfo(woeid, city, metric);
if (info != null) {
return info;
}
} catch (JSONException e) {
Log.e(TAG, "Received malformed placefinder data (location=" + location + ", lang=" + language + ")", e);
}
return null;
}
private LocationResult parsePlace(JSONObject place) throws JSONException {
LocationResult result = new LocationResult();
JSONObject country = place.getJSONObject("country");
result.id = place.getString("woeid");
result.country = country.getString("content");
result.countryId = country.getString("code");
if (!place.isNull("postal")) {
result.postal = place.getJSONObject("postal").getString("content");
}
for (String name : LOCALITY_NAMES) {
if (!place.isNull(name)) {
result.city = place.getJSONObject(name).getString("content");
break;
}
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "JSON data " + place.toString() + " -> id=" + result.id + ", city=" + result.city + ", country=" + result.countryId);
}
if (result.id == null || result.city == null || result.countryId == null) {
return null;
}
return result;
}
private JSONObject fetchResults(String url) {
String response = HttpRetriever.retrieve(url);
if (response == null) {
return null;
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Request URL is " + url + ", response is " + response);
}
try {
JSONObject rootObject = new JSONObject(response);
return rootObject.getJSONObject("query").getJSONObject("results");
} catch (JSONException e) {
Log.w(TAG, "Received malformed places data (url=" + url + ")", e);
}
return null;
}
private String getLanguage() {
Locale locale = mContext.getResources().getConfiguration().locale;
String country = locale.getCountry();
String language = locale.getLanguage();
if (TextUtils.isEmpty(country)) {
return language;
}
return language + "-" + country;
}
public static class HttpRetriever {
private static final String TAG = "HttpRetriever";
public static String retrieve(String url) {
HttpGet request = new HttpGet(url);
try {
HttpResponse response = new DefaultHttpClient().execute(request);
HttpEntity entity = response.getEntity();
if (entity != null) {
return EntityUtils.toString(entity);
}
} catch (IOException e) {
Log.e(TAG, "Couldn't retrieve data from url " + url, e);
}
return null;
}
}
}
@SemonCat
Copy link
Author

Port from CM Weather Widget.

Use getWeatherInfo(Location location, boolean metric) to get Weather info.

PS1. don't use function in main thread.
PS2. use WeatherInfo toString() to print field.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment