Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9754959f authored by Michael W's avatar Michael W
Browse files

OpenWeatherMapProvider: Fix API calls and responses

* The 16 day api which is currently called needs a paid-for account
* Use the free API w/ 5 days/3 hours forecast
  * The result-set was limited to 5 days anyway
* While reworking the API calls, remove unnecessary parameter "mode",
  which requests the API's default "json" anyway
* Modify the ForecastResponse class to be able to understand the new
  result
  * Remove class "Temp" - not used anymore
  * Introduce class "Main" which matches the new response's name and
    content
* Calculate the min/max temperatures for each day in the forecast manually
  as we get 5days à 3hours now
* Interpret wind speed correctly: result is m/s when requesting metric
  values but it's passed on (and interpreted) as kph, so multiply the
  speed w/ factor 3.6 in that case - imperial is already correct, no need
  to touch that

* Thanks to Justin Bouchard <jusking.bouchard@gmail.com> who found that
  the current API call isn't working properly

Change-Id: Ibdc3d3a3a4538fcc403dcb47c488baee8051d37e
parent 3fe41331
Loading
Loading
Loading
Loading
+20 −10
Original line number Diff line number Diff line
/*
 *  Copyright (C) 2016 The CyanogenMod Project
 *  Copyright (C) 2017 The LineageOS Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -33,10 +34,22 @@ public class ForecastResponse implements Serializable {

    static class DayForecast {

        @SerializedName("dt")
        private long timestamp;
        private Main main;
        private List<Weather> weather;
        private Temp temp;

        public DayForecast() {}

        static class Main {
            public Main() {}
            private double temp = Double.NaN;
            @SerializedName("temp_min")
            private double minTemp = Double.NaN;
            @SerializedName("temp_max")
            private double maxTemp = Double.NaN;
        }

        static class Weather {
            @SerializedName("id")
            private int code = WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
@@ -45,13 +58,6 @@ public class ForecastResponse implements Serializable {
            public Weather() {}
        }

        static class Temp {
            double min = Double.NaN;
            double max = Double.NaN;

            public Temp() {}
        }

        public int getConditionCode() {
            if (weather == null || weather.size() == 0) {
                return WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
@@ -69,11 +75,15 @@ public class ForecastResponse implements Serializable {
        }

        public double getMinTemp() {
            return temp.min;
            return main.minTemp;
        }

        public double getMaxTemp() {
            return temp.max;
            return main.maxTemp;
        }

        public long getTimestamp() {
            return timestamp;
        }
    }

+11 −13
Original line number Diff line number Diff line
/*
 *  Copyright (C) 2016 The CyanogenMod Project
 *  Copyright (C) 2017 The LineageOS Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -23,25 +24,22 @@ import retrofit2.http.Query;
public interface OpenWeatherMapInterface {
    @GET("/data/2.5/weather")
    Call<CurrentWeatherResponse> queryCurrentWeather(@Query("id") String cityId,
        @Query("mode") String mode, @Query("units") String units, @Query("lang") String lang,
            @Query("appid") String appid);
        @Query("units") String units, @Query("lang") String lang, @Query("appid") String appid);

    @GET("/data/2.5/weather")
    Call<CurrentWeatherResponse> queryCurrentWeather(@Query("lat") double lat,
        @Query("lon") double lon, @Query("mode") String mode, @Query("units") String units,
            @Query("lang") String lang, @Query("appid") String appid);

    @GET("/data/2.5/forecast/daily")
    Call<ForecastResponse> queryForecast(@Query("id") String cityId, @Query("mode") String mode,
        @Query("units") String units, @Query("lang") String lang, @Query("cnt") int daysCount,
        @Query("lon") double lon, @Query("units") String units, @Query("lang") String lang,
            @Query("appid") String appid);

    @GET("/data/2.5/forecast/daily")
    @GET("/data/2.5/forecast")
    Call<ForecastResponse> queryForecast(@Query("id") String cityId, @Query("units") String units,
        @Query("lang") String lang, @Query("appid") String appid);

    @GET("/data/2.5/forecast")
    Call<ForecastResponse> queryForecast(@Query("lat") double lat, @Query("lon") double lon,
        @Query("mode") String mode, @Query("units") String units, @Query("lang") String lang,
            @Query("cnt") int daysCount, @Query("appid") String appid);
        @Query("units") String units, @Query("lang") String lang, @Query("appid") String appid);

    @GET("/data/2.5/find")
    Call<LookupCityResponse> lookupCity(@Query("q") String cityName, @Query("mode") String mode,
        @Query("lang") String lang, @Query("type") String searchType, @Query("appid") String appid);
    Call<LookupCityResponse> lookupCity(@Query("q") String cityName, @Query("lang") String lang,
        @Query("type") String searchType, @Query("appid") String appid);
}
+73 −24
Original line number Diff line number Diff line
/*
 *  Copyright (C) 2016 The CyanogenMod Project
 *  Copyright (C) 2017 The LineageOS Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -24,6 +25,7 @@ import org.lineageos.openweathermapprovider.utils.Logging;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -42,10 +44,9 @@ import retrofit2.converter.gson.GsonConverterFactory;

public class OpenWeatherMapService {

    private static final String RESULT_FORMAT = "json";
    private static final int FORECAST_ITEMS_PER_DAY = 8;

    // TODO Add an preference in settings to customize this
    private static final int FORECAST_DAYS = 5;
    private static final double MPS_TO_KPH = 3.6;

    // OpenWeatherMap allows like or accurate, let's use like so we return more choices to the user
    private static final String SEARCH_CITY_TYPE = "like";
@@ -85,7 +86,7 @@ public class OpenWeatherMapService {
        final String units = mapTempUnit(tempUnit);
        Call<CurrentWeatherResponse> weatherResponseCall
                = mOpenWeatherMapInterface.queryCurrentWeather(weatherLocation.getCityId(),
                RESULT_FORMAT, units, language, mApiKey);
                units, language, mApiKey);
        Response<CurrentWeatherResponse> currentWeatherResponse;
        try {
            Logging.logd(weatherResponseCall.request().toString());
@@ -100,7 +101,7 @@ public class OpenWeatherMapService {
            //but the user is expecting both the current weather and the forecast
            Call<ForecastResponse> forecastResponseCall
                    = mOpenWeatherMapInterface.queryForecast(weatherLocation.getCityId(),
                    RESULT_FORMAT, units, language, FORECAST_DAYS, mApiKey);
                    units, language, mApiKey);
            ForecastResponse forecastResponse = null;
            try {
                Logging.logd(forecastResponseCall.request().toString());
@@ -133,7 +134,7 @@ public class OpenWeatherMapService {
        final String units = mapTempUnit(tempUnit);
        Call<CurrentWeatherResponse> weatherResponseCall
                = mOpenWeatherMapInterface.queryCurrentWeather(location.getLatitude(),
                location.getLongitude(), RESULT_FORMAT, units, language, mApiKey);
                location.getLongitude(), units, language, mApiKey);
        Response<CurrentWeatherResponse> currentWeatherResponse;
        try {
            Logging.logd(weatherResponseCall.request().toString());
@@ -149,7 +150,7 @@ public class OpenWeatherMapService {
            //but the user is expecting both the current weather and the forecast
            Call<ForecastResponse> forecastResponseCall
                    = mOpenWeatherMapInterface.queryForecast(location.getLatitude(),
                    location.getLongitude(), RESULT_FORMAT, units, language, FORECAST_DAYS, mApiKey);
                    location.getLongitude(), units, language, mApiKey);
            ForecastResponse forecastResponse = null;
            try {
                Logging.logd(forecastResponseCall.request().toString());
@@ -183,10 +184,9 @@ public class OpenWeatherMapService {
        WeatherInfo.Builder builder = new WeatherInfo.Builder(cityName,
                sanitizeTemperature(temperature, true), tempUnit)
                        .setTimestamp(System.currentTimeMillis());

        builder.setWeatherCondition(mapConditionIconToCode(
                currentWeatherResponse.getWeatherIconId(),
                        currentWeatherResponse.getConditionCode()));
        final int condition = mapConditionIconToCode(currentWeatherResponse.getWeatherIconId(),
                currentWeatherResponse.getConditionCode());
        builder.setWeatherCondition(condition);

        final double humidity = currentWeatherResponse.getHumidity();
        if (!Double.isNaN(humidity)) {
@@ -204,29 +204,79 @@ public class OpenWeatherMapService {
        }

        final double windDir = currentWeatherResponse.getWindDirection();
        final double windSpeed = currentWeatherResponse.getWindSpeed();
        double windSpeed = currentWeatherResponse.getWindSpeed();
        if (!Double.isNaN(windDir) && !Double.isNaN(windSpeed)) {
            if (tempUnit == WeatherContract.WeatherColumns.TempUnit.CELSIUS) {
                windSpeed *= MPS_TO_KPH;
            }
            builder.setWind(windSpeed, windDir, WeatherContract.WeatherColumns.WindSpeedUnit.KPH);
        }

        if (forecastResponse != null) {
            List<WeatherInfo.DayForecast> forecastList = new ArrayList<>();
            for (ForecastResponse.DayForecast forecast : forecastResponse.getForecastList()) {
                WeatherInfo.DayForecast.Builder forecastBuilder
                        = new WeatherInfo.DayForecast.Builder(mapConditionIconToCode(
                                forecast.getWeatherIconId(), forecast.getConditionCode()));
            List<ForecastResponse.DayForecast> forecastResponses =
                    forecastResponse.getForecastList();
            double dayMinimum = Double.NaN;
            double dayMaximum = Double.NaN;
            WeatherInfo.DayForecast.Builder forecastBuilder = null;
            int maxItems = forecastResponses.size();
            for (int i = 0; i < maxItems; i++) {
                ForecastResponse.DayForecast forecast = forecastResponses.get(i);

                Calendar forecastCalendar = Calendar.getInstance();
                forecastCalendar.setTimeInMillis(forecast.getTimestamp() * 1000);

                // If the first forecast item is for the next day, add a forecast item with
                // today's values so the list is populated correctly.
                if (i == 0) {
                    int forecastDay = forecastCalendar.get(Calendar.DAY_OF_YEAR);
                    int currentDay = Calendar.getInstance().get(Calendar.DAY_OF_YEAR);
                    if (currentDay != forecastDay) {
                        forecastBuilder = new WeatherInfo.DayForecast.Builder(condition);
                        if (!Double.isNaN(todaysHigh)) {
                            forecastBuilder.setHigh(todaysHigh);
                        }
                        if (!Double.isNaN(todaysLow)) {
                            forecastBuilder.setLow(todaysLow);
                        }
                        forecastList.add(forecastBuilder.build());

                        // Remove items from the list so we add the forecast for 5 days only
                        maxItems -= FORECAST_ITEMS_PER_DAY;
                    }
                }

                final double max = forecast.getMaxTemp();
                if (!Double.isNaN(max)) {
                    forecastBuilder.setHigh(max);
                if (!Double.isNaN(max) && (Double.isNaN(dayMaximum) || max > dayMaximum)) {
                    dayMaximum = max;
                }

                final double min = forecast.getMinTemp();
                if (!Double.isNaN(min)) {
                    forecastBuilder.setLow(min);
                if (!Double.isNaN(min) && (Double.isNaN(dayMinimum) || min < dayMinimum)) {
                    dayMinimum = min;
                }

                // Every 8th (8 x 3h = 24h) time create the builder with the result's weather
                // so you get a forecast for the same time every day
                if (i % FORECAST_ITEMS_PER_DAY == 0) {
                    forecastBuilder = new WeatherInfo.DayForecast.Builder(mapConditionIconToCode(
                            forecast.getWeatherIconId(), forecast.getConditionCode()));
                }

                // If it's the last result of each day (within 3 hours from the next day),
                // build the forecast and add the calculated min and max temperatures
                int forecastHour = forecastCalendar.get(Calendar.HOUR_OF_DAY);
                if (forecastHour >= 21) {
                    if (!Double.isNaN(dayMinimum)) {
                        forecastBuilder.setLow(dayMinimum);
                    }
                    if (!Double.isNaN(dayMaximum)) {
                        forecastBuilder.setHigh(dayMaximum);
                    }
                    forecastList.add(forecastBuilder.build());
                    dayMinimum = Double.NaN;
                    dayMaximum = Double.NaN;
                }
            }
            builder.setForecast(forecastList);
        }
@@ -245,9 +295,8 @@ public class OpenWeatherMapService {
            throw new InvalidApiKeyException();
        }

        Call<LookupCityResponse> lookupCityCall
                = mOpenWeatherMapInterface.lookupCity(cityName, RESULT_FORMAT, getLanguageCode(),
                        SEARCH_CITY_TYPE, mApiKey);
        Call<LookupCityResponse> lookupCityCall = mOpenWeatherMapInterface.lookupCity(
                cityName, getLanguageCode(), SEARCH_CITY_TYPE, mApiKey);

        Response<LookupCityResponse> lookupResponse;
        try {