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
/*
* 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;
}
}
......
/*
* 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);
}
/*
* 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;
}
forecastList.add(forecastBuilder.build());
// 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 {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment