com.versobit.weatherdoge.WeatherUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.versobit.weatherdoge.WeatherUtil.java

Source

/*
 * Copyright (C) 2014 VersoBit Ltd
 *
 * This file is part of Weather Doge.
 *
 * Weather Doge is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Weather Doge is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Weather Doge.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.versobit.weatherdoge;

import android.util.Log;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.json.JSONObject;

import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.HttpsURLConnection;

final class WeatherUtil {

    private static final String TAG = WeatherUtil.class.getSimpleName();

    private static final Pattern YAHOO_TIME = Pattern.compile("([0-9]{1,2}):([0-9]{2}) (am|pm)");
    private static final SimpleDateFormat YAHOO_DATE_FORMAT = new SimpleDateFormat("EEE, d MMM yyyy h:m a zzz");

    private WeatherUtil() {
        //
    }

    // Fetch weather from a general location search string. Source dependent. Your mileage may vary.
    static WeatherResult getWeather(String location, Source source) {
        return getWeather(Double.MIN_VALUE, Double.MIN_VALUE, location, source);
    }

    // Fetch weather from geographic coordinates
    static WeatherResult getWeather(double latitude, double longitude, Source source) {
        return getWeather(latitude, longitude, null, source);
    }

    private static WeatherResult getWeather(double latitude, double longitude, String location, Source source) {
        switch (source) {
        case OPEN_WEATHER_MAP:
            return getWeatherFromOWM(latitude, longitude, location);
        case YAHOO:
            return getWeatherFromYahoo(latitude, longitude, location);
        }
        throw new IllegalArgumentException("No supported weather source provided.");
    }

    private static WeatherResult getWeatherFromYahoo(double latitude, double longitude, String location) {
        try {
            String yqlText;
            if (latitude == Double.MIN_VALUE && longitude == Double.MIN_VALUE) {
                if (location == null) {
                    return new WeatherResult(null, WeatherResult.ERROR_THROWABLE, "No valid location parameters.",
                            new IllegalArgumentException());
                }
                yqlText = location.replaceAll("[^\\p{L}\\p{Nd} ,-]+", "");
            } else {
                yqlText = String.valueOf(latitude) + ", " + String.valueOf(longitude);
            }
            URL url = new URL("https://query.yahooapis.com/v1/public/yql?q=" + URLEncoder.encode(
                    "select location.city, units, item.condition, astronomy from weather.forecast where woeid in (select woeid from geo.placefinder where text = \""
                            + yqlText + "\" and gflags = \"R\" limit 1) limit 1",
                    "UTF-8") + "&format=json");
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            try {
                JSONObject response = new JSONObject(IOUtils.toString(connection.getInputStream()));
                if (connection.getResponseCode() != HttpsURLConnection.HTTP_OK) {
                    JSONObject error = response.getJSONObject("error");
                    return new WeatherResult(null, WeatherResult.ERROR_API, error.getString("description"), null);
                }
                JSONObject query = response.getJSONObject("query");
                if (query.getInt("count") == 0) {
                    return new WeatherResult(null, WeatherResult.ERROR_API, "No results found for that location.",
                            null);
                }

                JSONObject channel = query.getJSONObject("results").getJSONObject("channel");
                JSONObject units = channel.getJSONObject("units");
                JSONObject condition = channel.getJSONObject("item").getJSONObject("condition");
                JSONObject astronomy = channel.getJSONObject("astronomy");

                double temp = condition.getDouble("temp");
                if ("F".equals(units.getString("temperature"))) {
                    temp = (temp - 32d) * 5d / 9d;
                }
                String text = condition.getString("text");
                String code = condition.getString("code");
                String date = condition.getString("date");
                String sunrise = astronomy.getString("sunrise");
                String sunset = astronomy.getString("sunset");

                String owmCode = convertYahooCode(code, date, sunrise, sunset);

                if (location == null || location.isEmpty()) {
                    location = channel.getJSONObject("location").getString("city");
                }

                return new WeatherResult(new WeatherData(temp, text, owmCode, latitude, longitude, location,
                        new Date(), Source.YAHOO), WeatherResult.ERROR_NONE, null, null);
            } finally {
                connection.disconnect();
            }
        } catch (Exception ex) {
            return new WeatherResult(null, WeatherResult.ERROR_THROWABLE, ex.getMessage(), ex);
        }
    }

    private static WeatherResult getWeatherFromOWM(double latitude, double longitude, String location) {
        try {
            String query;
            if (latitude == Double.MIN_VALUE && longitude == Double.MIN_VALUE) {
                if (location == null) {
                    return new WeatherResult(null, WeatherResult.ERROR_THROWABLE, "No valid location parameters.",
                            new IllegalArgumentException());
                }
                query = "q=" + URLEncoder.encode(location, "UTF-8");
            } else {
                query = "lat=" + URLEncoder.encode(String.valueOf(latitude), "UTF-8") + "&lon="
                        + URLEncoder.encode(String.valueOf(longitude), "UTF-8");
            }
            query += "&APPID=" + URLEncoder.encode(BuildConfig.OWM_APPID, "UTF-8");
            URL url = new URL("http://api.openweathermap.org/data/2.5/weather?" + query);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            try {
                JSONObject response = new JSONObject(IOUtils.toString(connection.getInputStream()));
                if (response.getInt("cod") != HttpURLConnection.HTTP_OK) {
                    // OWM has HTTP error codes that are passed through an API field, the actual HTTP
                    // error code is always 200...
                    return new WeatherResult(null, WeatherResult.ERROR_API,
                            response.getString("cod") + ": " + response.getString("message"), null);
                }

                JSONObject weather = response.getJSONArray("weather").getJSONObject(0);
                JSONObject main = response.getJSONObject("main");

                double temp = main.getDouble("temp") - 273.15d;
                String condition = WordUtils.capitalize(weather.getString("description").trim());
                String image = weather.getString("icon");

                if (location == null || location.isEmpty()) {
                    location = response.getString("name");
                }

                return new WeatherResult(new WeatherData(temp, condition, image, latitude, longitude, location,
                        new Date(), Source.OPEN_WEATHER_MAP), WeatherResult.ERROR_NONE, null, null);
            } finally {
                connection.disconnect();
            }
        } catch (Exception ex) {
            return new WeatherResult(null, WeatherResult.ERROR_THROWABLE, ex.getMessage(), ex);
        }
    }

    private static String convertYahooCode(String code, String weatherTime, String sunrise, String sunset) {
        Date weatherDate = new Date();
        try {
            weatherDate = YAHOO_DATE_FORMAT.parse(weatherTime);
        } catch (ParseException ex) {
            Log.e(TAG, "Yahoo date format failed!", ex);
        }
        Calendar weatherCal = new GregorianCalendar();
        Calendar sunriseCal = new GregorianCalendar();
        Calendar sunsetCal = new GregorianCalendar();
        weatherCal.setTime(weatherDate);
        sunriseCal.setTime(weatherDate);
        sunsetCal.setTime(weatherDate);

        Matcher sunriseMatch = YAHOO_TIME.matcher(sunrise);
        Matcher sunsetMatch = YAHOO_TIME.matcher(sunset);
        if (!sunriseMatch.matches() || !sunsetMatch.matches()) {
            Log.e(TAG, "Failed to find sunrise/sunset. Using defaults.");
            sunriseMatch = YAHOO_TIME.matcher("6:00 am");
            sunsetMatch = YAHOO_TIME.matcher("6:00 pm");
            sunriseMatch.matches();
            sunsetMatch.matches();
        }
        // Set the sunrise to the correct hour and minute of the same day
        sunriseCal.set(Calendar.HOUR, Integer.parseInt(sunriseMatch.group(1)));
        sunriseCal.set(Calendar.MINUTE, Integer.parseInt(sunriseMatch.group(2)));
        sunriseCal.set(Calendar.SECOND, 0);
        sunriseCal.set(Calendar.MILLISECOND, 0);
        sunriseCal.set(Calendar.AM_PM, "am".equals(sunriseMatch.group(3)) ? Calendar.AM : Calendar.PM);

        // Set the sunset to the correct hour and minute of the same day
        sunsetCal.set(Calendar.HOUR, Integer.parseInt(sunsetMatch.group(1)));
        sunsetCal.set(Calendar.MINUTE, Integer.parseInt(sunsetMatch.group(2)));
        sunsetCal.set(Calendar.SECOND, 0);
        sunsetCal.set(Calendar.MILLISECOND, 0);
        sunsetCal.set(Calendar.AM_PM, "am".equals(sunsetMatch.group(3)) ? Calendar.AM : Calendar.PM);

        boolean isDaytime = true;
        if (weatherCal.before(sunriseCal) || weatherCal.after(sunsetCal)) {
            isDaytime = false;
        }

        String owmCode = "01";
        switch (Integer.parseInt(code)) {
        // Thunderstorms
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 17:
        case 37:
        case 38:
        case 39:
        case 45:
        case 47:
            owmCode = "11";
            break;
        // Snow
        case 5:
        case 7:
        case 13:
        case 14:
        case 15:
        case 16:
        case 18:
        case 41:
        case 42:
        case 43:
        case 46:
            owmCode = "13";
            break;
        // Rain
        case 6:
        case 10:
        case 35:
            owmCode = "09";
            break;
        // Light-ish Rain
        case 8:
        case 9:
        case 11:
        case 12:
        case 40:
            owmCode = "10";
            break;
        // Fog
        case 19:
        case 20:
        case 21:
        case 22:
            owmCode = "50";
            break;
        // Cloudy
        case 27:
        case 28:
            owmCode = "04";
            break;
        // (Other) Cloudy
        case 26:
            owmCode = "03";
            break;
        // Partly Cloudy
        case 23:
        case 24:
        case 25:
        case 29:
        case 30:
        case 44:
            owmCode = "02";
            break;
        // Clear
        case 31:
        case 32:
        case 33:
        case 34:
        case 36:
            owmCode = "01";
            break;
        }
        return owmCode + (isDaytime ? "d" : "n");
    }

    enum Source {
        OPEN_WEATHER_MAP, YAHOO
    }

    final static class WeatherResult {
        final static int ERROR_NONE = 0;
        final static int ERROR_API = 1;
        final static int ERROR_THROWABLE = 2;

        final WeatherData data; // null unless error = ERROR_NONE
        final int error; // corresponds to the above error codes or ERROR_NONE for no error
        final String msg; // null if ERROR_NONE, may or may not be null if there's an error
        final Throwable throwable; // null unless error = ERROR_THROWABLE

        WeatherResult(WeatherData data, int error, String msg, Throwable throwable) {
            this.data = data;
            this.error = error;
            this.msg = msg;
            this.throwable = throwable;
        }
    }

    final static class WeatherData implements Serializable {
        private static final long serialVersionUID = 7141137302093960119L;

        final double temperature; // Always in Celsius
        final String condition;
        final String image; // An OpenWeatherMap weather icon ID
        final double latitude;
        final double longitude;
        final String place;
        final Date time; // The system time the data was retrieved
        final Source source;

        WeatherData(double temperature, String condition, String image, double latitude, double longitude,
                String place, Date time, Source source) {
            this.temperature = temperature;
            this.condition = condition;
            this.image = image;
            this.latitude = latitude;
            this.longitude = longitude;
            this.place = place;
            this.time = time;
            this.source = source;
        }

        @Override
        public boolean equals(Object o) {
            if (o == null) {
                return false;
            }
            if (getClass() != o.getClass()) {
                return false;
            }
            final WeatherData other = (WeatherData) o;
            if ((this.temperature != other.temperature) || !this.condition.equals(other.condition)
                    || !this.image.equals(other.image) || (this.latitude != other.latitude)
                    || (this.longitude != other.longitude) || !this.place.equals(other.place)
                    || !this.time.equals(other.time) || (this.source != other.source)) {
                return false;
            }
            return true;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder(getClass().getSimpleName());
            sb.append("[temperature=").append(temperature).append(", condition=").append(condition)
                    .append(", image=").append(image).append(", latitude=").append(latitude).append(", longitude=")
                    .append(longitude).append(", place=").append(place).append(", time=").append(time)
                    .append(", source=").append(source).append("]");
            return sb.toString();
        }
    }
}