From afcbab84ad3b3516c20d484aed4e560917adc2e2 Mon Sep 17 00:00:00 2001
From: Tor-Einar Skog <tor-einar.skog@nibio.no>
Date: Mon, 21 Jan 2019 13:10:00 +0100
Subject: [PATCH] Fixed handling of weather data from forecasts, particularly
 for the GRID humidity model

---
 ...ptoriaSimpleRiskGridModelPreprocessor.java |  8 ++--
 .../util/weather/WeatherDataSourceUtil.java   | 48 ++++++++++++++++---
 .../weather/YrWeatherForecastProvider.java    | 11 +++--
 .../dnmipointweb/DMIPointWebDataParser.java   | 46 ++++++++++++------
 .../nordic_septoria_whs.js                    |  2 +-
 .../YrWeatherForecastProviderTest.java        | 18 +++----
 6 files changed, 93 insertions(+), 40 deletions(-)

diff --git a/src/main/java/no/nibio/vips/logic/scheduling/model/grid/preprocessor/ZymoseptoriaSimpleRiskGridModelPreprocessor.java b/src/main/java/no/nibio/vips/logic/scheduling/model/grid/preprocessor/ZymoseptoriaSimpleRiskGridModelPreprocessor.java
index e2e28760..2abdd427 100644
--- a/src/main/java/no/nibio/vips/logic/scheduling/model/grid/preprocessor/ZymoseptoriaSimpleRiskGridModelPreprocessor.java
+++ b/src/main/java/no/nibio/vips/logic/scheduling/model/grid/preprocessor/ZymoseptoriaSimpleRiskGridModelPreprocessor.java
@@ -23,7 +23,6 @@ import com.vividsolutions.jts.geom.Coordinate;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoUnit;
-import java.time.temporal.TemporalUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
@@ -128,11 +127,12 @@ public class ZymoseptoriaSimpleRiskGridModelPreprocessor extends ModelRunPreproc
                     }, 
                     Date.from(aWeekAgo.toInstant()),
                     Date.from(aWeekAhead.toInstant()),
-                    true
+                    true,
+                    new HashSet<>(Arrays.asList(WeatherObservation.LOG_INTERVAL_ID_1H,WeatherObservation.LOG_INTERVAL_ID_3H, WeatherObservation.LOG_INTERVAL_ID_6H))
             );
-            stationObs = wUtil.checkForAndFixHourlyTimeSeriesHoles(stationObs);
+            //stationObs = wUtil.checkForAndFixHourlyTimeSeriesHoles(stationObs,6);
             return stationObs;
-        } catch (WeatherDataSourceException | WeatherObservationListException ex ) {
+        } catch (WeatherDataSourceException  ex ) {
             throw new PreprocessorException(ex.getMessage());
         }
     }
diff --git a/src/main/java/no/nibio/vips/util/weather/WeatherDataSourceUtil.java b/src/main/java/no/nibio/vips/util/weather/WeatherDataSourceUtil.java
index 406dc962..ad85e6ae 100755
--- a/src/main/java/no/nibio/vips/util/weather/WeatherDataSourceUtil.java
+++ b/src/main/java/no/nibio/vips/util/weather/WeatherDataSourceUtil.java
@@ -33,8 +33,10 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TimeZone;
 import no.nibio.vips.entity.WeatherObservation;
 import no.nibio.vips.logic.entity.PointOfInterestWeatherStation;
@@ -68,13 +70,16 @@ public class WeatherDataSourceUtil {
      * @return
      * @throws PreprocessorException
      */
-    public List<WeatherObservation> getWeatherObservations(PointOfInterestWeatherStation station, Integer logIntervalId, String[] elementMeasurementTypes, Date startTime, Date endTime, Boolean ignoreErrors) throws WeatherDataSourceException {
-        List<WeatherObservation> observations = this.getWeatherObservations(station.getDataFetchUri(), logIntervalId, elementMeasurementTypes, startTime, endTime, TimeZone.getTimeZone(station.getTimeZone()), ignoreErrors);
+    public List<WeatherObservation> getWeatherObservations(PointOfInterestWeatherStation station, Integer logIntervalId, String[] elementMeasurementTypes, Date startTime, Date endTime, Boolean ignoreErrors, Set<Integer> toleratedLogIntervalIds) throws WeatherDataSourceException {
+        // Get measured (and possibly forecasted, depending on the data source) observations
+        List<WeatherObservation> observations = this.getWeatherObservations(station.getDataFetchUri(), logIntervalId, elementMeasurementTypes, startTime, endTime, TimeZone.getTimeZone(station.getTimeZone()), ignoreErrors, toleratedLogIntervalIds);
         Collections.sort(observations);
+        // Append forecasts, if available
         Date latestTimeOfMeasuredObservations = observations.isEmpty() ? null : observations.get(observations.size() - 1).getTimeMeasured();
         //System.out.println("latestTimeOfMeasuredObservations = " + latestTimeOfMeasuredObservations);
         // Todo: We don't collect forecast data when the endTime is before the actual, current date (not systemdate)
-        if (station.getWeatherForecastProviderId() != null && ! SystemTime.isSystemTimeOffsetFromNow()) {
+        if (station.getWeatherForecastProviderId() != null && ! SystemTime.isSystemTimeOffsetFromNow()) 
+        {
             try {
                 WeatherForecastProvider forecastProvider = WeatherStationProviderFactory.getWeatherForecastProvider(station.getWeatherForecastProviderId().getWeatherForecastProviderId());
                 List<WeatherObservation> forecasts = forecastProvider.getWeatherForecasts(station);
@@ -83,7 +88,7 @@ public class WeatherDataSourceUtil {
                     obsMap.put(elementMeasurementType, new ArrayList<>());
                 }
                 forecasts.stream().filter((obs) -> (
-                        obs.getLogIntervalId().equals(logIntervalId) && obsMap.get(obs.getElementMeasurementTypeId()) != null)
+                        toleratedLogIntervalIds.contains(obs.getLogIntervalId()) && obsMap.get(obs.getElementMeasurementTypeId()) != null)
                     ).forEachOrdered((obs) -> {
                         obsMap.get(obs.getElementMeasurementTypeId()).add(obs);
                     });
@@ -120,6 +125,21 @@ public class WeatherDataSourceUtil {
         }
         return observations;
     }
+    
+    /**
+     * The intolerant version. Only accept observations with same logIntervalId as requested
+     * @param station
+     * @param logIntervalId
+     * @param elementMeasurementTypes
+     * @param startTime
+     * @param endTime
+     * @param ignoreErrors
+     * @return
+     * @throws WeatherDataSourceException 
+     */
+    public List<WeatherObservation> getWeatherObservations(PointOfInterestWeatherStation station, Integer logIntervalId, String[] elementMeasurementTypes, Date startTime, Date endTime, Boolean ignoreErrors) throws WeatherDataSourceException {
+        return this.getWeatherObservations(station, logIntervalId, elementMeasurementTypes, startTime, endTime, ignoreErrors, new HashSet<>(Arrays.asList(logIntervalId)));
+    }
 
     /**
      * Fetches measured data from the stations weather data source, and optionally
@@ -149,6 +169,22 @@ public class WeatherDataSourceUtil {
         return new ObjectMapper().readValue(JSONtext, new TypeReference<List<WeatherObservation>>() {
         });
     }
+    
+    /**
+     * The intolerant version. Only accept observations with same logIntervalId as requested
+     * @param fetchURI
+     * @param logIntervalId
+     * @param elementMeasurementTypes
+     * @param startTime
+     * @param endTime
+     * @param timeZone
+     * @param ignoreErrors
+     * @return
+     * @throws WeatherDataSourceException 
+     */
+    public List<WeatherObservation> getWeatherObservations(String fetchURI, Integer logIntervalId, String[] elementMeasurementTypes, Date startTime, Date endTime, TimeZone timeZone, Boolean ignoreErrors) throws WeatherDataSourceException {
+        return this.getWeatherObservations(fetchURI, logIntervalId, elementMeasurementTypes, startTime, endTime, timeZone, ignoreErrors, new HashSet<>(Arrays.asList(logIntervalId)));
+    }
 
     /**
      * Collects weather observations from a data source
@@ -160,7 +196,7 @@ public class WeatherDataSourceUtil {
      * @param timeZone
      * @return
      */
-    public List<WeatherObservation> getWeatherObservations(String fetchURI, Integer logIntervalId, String[] elementMeasurementTypes, Date startTime, Date endTime, TimeZone timeZone, Boolean ignoreErrors) throws WeatherDataSourceException {
+    public List<WeatherObservation> getWeatherObservations(String fetchURI, Integer logIntervalId, String[] elementMeasurementTypes, Date startTime, Date endTime, TimeZone timeZone, Boolean ignoreErrors, Set<Integer> toleratedLogIntervalIds) throws WeatherDataSourceException {
         SimpleDateFormat dateOutput = new SimpleDateFormat("yyyy-MM-dd");
         dateOutput.setTimeZone(timeZone);
         SimpleDateFormat hourOutput = new SimpleDateFormat("H");
@@ -199,7 +235,7 @@ public class WeatherDataSourceUtil {
             List<WeatherObservation> filteredObservations = new ArrayList<>();
             //System.out.println(this.getClass().getName() + "/preliminaryResult.size()=" + preliminaryResult.size());
             preliminaryResult.stream().filter((candidateObs) -> (
-                    candidateObs.getLogIntervalId().equals(logIntervalId)
+                    toleratedLogIntervalIds.contains(candidateObs.getLogIntervalId())
                     && Arrays.asList(elementMeasurementTypes).contains(candidateObs.getElementMeasurementTypeId())
                     )
                 ).forEachOrdered((candidateObs) -> {
diff --git a/src/main/java/no/nibio/vips/util/weather/YrWeatherForecastProvider.java b/src/main/java/no/nibio/vips/util/weather/YrWeatherForecastProvider.java
index 5b17b7f1..c3db63a0 100755
--- a/src/main/java/no/nibio/vips/util/weather/YrWeatherForecastProvider.java
+++ b/src/main/java/no/nibio/vips/util/weather/YrWeatherForecastProvider.java
@@ -77,7 +77,7 @@ public class YrWeatherForecastProvider implements WeatherForecastProvider{
                     YrWeatherForecastProvider.YR_API_URL, 
                     latitude,
                     longitude,
-                    altitude)
+                    altitude.intValue())
             );
             
             //System.out.println("yrURL=" + yrURL.toString());
@@ -94,6 +94,7 @@ public class YrWeatherForecastProvider implements WeatherForecastProvider{
             Long sixHoursDelta = 6 * hourDelta;
             SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
             Map<Date, WeatherObservation> RRMap = new HashMap<>();
+            Date earliestHourlyPrecipitationObservation = null;
             for(int i=0;i<nodes.getLength();i++)
             {
                 Node node = nodes.item(i);
@@ -138,14 +139,14 @@ public class YrWeatherForecastProvider implements WeatherForecastProvider{
                     RR.setElementMeasurementTypeId(WeatherElements.PRECIPITATION);
                     RR.setValue(Double.parseDouble(DOMUtils.getNodeAttr("precipitation","value",node2.getChildNodes())));
                     //System.out.println("Timediff=" + (toTime.getTime() - fromTime.getTime()));
-                    Date earliestHourlyObservation = null;
+                    
                     if(toTime.getTime() - fromTime.getTime() == hourDelta)
                     {
                         //System.out.println("Found 1 hour record at " + fromTime);
                         RR.setLogIntervalId(WeatherObservation.LOG_INTERVAL_ID_1H);
-                        if(earliestHourlyObservation == null)
+                        if(earliestHourlyPrecipitationObservation == null)
                         {
-                            earliestHourlyObservation = RR.getTimeMeasured();
+                            earliestHourlyPrecipitationObservation = RR.getTimeMeasured();
                         }
                     }
                     else if(toTime.getTime() - fromTime.getTime() == threeHoursDelta)
@@ -165,7 +166,7 @@ public class YrWeatherForecastProvider implements WeatherForecastProvider{
                     // In order to avoid overestimation of rain in the beginning,
                     // We skip ahead until we find observations with 1 hour resolution
                     if(!RR.getLogIntervalId().equals(WeatherObservation.LOG_INTERVAL_ID_1H)
-                            && (earliestHourlyObservation == null || earliestHourlyObservation.after(RR.getTimeMeasured()))
+                            && (earliestHourlyPrecipitationObservation == null || earliestHourlyPrecipitationObservation.after(RR.getTimeMeasured()))
                             )
                     {
                         continue;
diff --git a/src/main/java/no/nibio/vips/util/weather/dnmipointweb/DMIPointWebDataParser.java b/src/main/java/no/nibio/vips/util/weather/dnmipointweb/DMIPointWebDataParser.java
index 36e7a08a..cff4c3f0 100644
--- a/src/main/java/no/nibio/vips/util/weather/dnmipointweb/DMIPointWebDataParser.java
+++ b/src/main/java/no/nibio/vips/util/weather/dnmipointweb/DMIPointWebDataParser.java
@@ -22,19 +22,23 @@ package no.nibio.vips.util.weather.dnmipointweb;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TimeZone;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import java.util.stream.Collectors;
 import javax.xml.datatype.DatatypeFactory;
 import javax.xml.datatype.XMLGregorianCalendar;
 import no.nibio.vips.entity.WeatherObservation;
 import no.nibio.vips.util.WeatherElements;
+import no.nibio.vips.util.WeatherUtil;
 
 /**
  * Gets data from the Danish Meteorological Institute's Point Web service
@@ -58,7 +62,7 @@ public class DMIPointWebDataParser {
     
     public List<WeatherObservation> getData(Double longitude, Double latitude, Date dateFrom, Date dateTo)
     {
-        List<WeatherObservation> allObservations = new ArrayList<>();
+        List<WeatherObservation> rawObservations = new ArrayList<>();
         TimeZone danishTZ = TimeZone.getTimeZone("Europe/Copenhagen");
         try {
             IWeatherService proxy = new WeatherService().getSslOffloadedBasicHttpBindingIWeatherService();
@@ -106,7 +110,7 @@ public class DMIPointWebDataParser {
                                 WeatherObservation obs = this.getWeatherObservation(VIPSParam, weatherDataModel);
                                 if(obs != null)
                                 {
-                                    allObservations.add(obs);
+                                    rawObservations.add(obs);
                                 }
                             }
                     );
@@ -118,22 +122,34 @@ public class DMIPointWebDataParser {
         }
         //System.out.println("Number of extracted weather data = " + retVal.size());
         // After "now", the DMI service provides forecast values seamlessly. After approx 48 hours these
-        // values turn into 6 hour intervals, which we can't use. We need to weed them out
-        Collections.sort(allObservations);
+        // values turn into 6 hour intervals
+        // 2019-01-18: We keep all observations, leave it up to the client to decide what to do with the data.
+        // But we mark them with the correct measuring interval
+        
         ZonedDateTime now = ZonedDateTime.now();
-        ZonedDateTime lastValidObsTime = null;
         ZoneId danishZ = ZoneId.of("Europe/Copenhagen");
-        List<WeatherObservation> filteredObservations = new ArrayList<>();
-        for(WeatherObservation obs:allObservations)
-        {
-            ZonedDateTime obsTime = ZonedDateTime.ofInstant(obs.getTimeMeasured().toInstant(),danishZ);
-            if(lastValidObsTime == null || obsTime.isBefore(now) || obsTime.minusHours(6).isBefore(lastValidObsTime))
+        //List<WeatherObservation> filteredObservations = new ArrayList<>();
+        WeatherUtil wUtil = new WeatherUtil();
+        List<WeatherObservation> retVal = new ArrayList<>();
+        Set<String> parametersInResult = rawObservations.stream().map(obs->obs.getElementMeasurementTypeId()).collect(Collectors.toSet());
+        parametersInResult.forEach((param) -> {
+            ZonedDateTime lastObsTime = null;
+            List<WeatherObservation> paramObs = wUtil.filterWeatherObservationsByParameter(rawObservations, param);
+            Collections.sort(paramObs);
+            for(WeatherObservation obs:paramObs)
             {
-                filteredObservations.add(obs);
-                lastValidObsTime = obsTime;
-            }            
-        }
-        return allObservations;
+                ZonedDateTime obsTime = ZonedDateTime.ofInstant(obs.getTimeMeasured().toInstant(),danishZ);
+
+                if(lastObsTime != null && obsTime.isAfter(now) && !obsTime.minusHours(6).isBefore(lastObsTime))
+                {
+                    obs.setLogIntervalId(WeatherObservation.LOG_INTERVAL_ID_6H);
+                }
+                retVal.add(obs);
+                lastObsTime = obsTime;         
+            }
+        });
+        Collections.sort(retVal);
+        return retVal;
     }
 
     private WeatherObservation getWeatherObservation(String VIPSParam, WeatherDataModel wDataModel) {
diff --git a/src/main/webapp/public/nordic_septoria_whs/nordic_septoria_whs.js b/src/main/webapp/public/nordic_septoria_whs/nordic_septoria_whs.js
index a11212bb..509bee5e 100644
--- a/src/main/webapp/public/nordic_septoria_whs/nordic_septoria_whs.js
+++ b/src/main/webapp/public/nordic_septoria_whs/nordic_septoria_whs.js
@@ -192,7 +192,7 @@ function getTodayAtMidnight()
     return new Date(midnightString);
     
     // OR RETURN A FIXED DATE FOR TESTING
-    //return new Date("2017-09-07T22:00:00.000+0000");
+    //return new Date("2018-08-16T22:00:00.000+0000");
 }
 
 
diff --git a/src/test/java/no/nibio/vips/util/weather/YrWeatherForecastProviderTest.java b/src/test/java/no/nibio/vips/util/weather/YrWeatherForecastProviderTest.java
index 8cc2534d..b7294e0c 100755
--- a/src/test/java/no/nibio/vips/util/weather/YrWeatherForecastProviderTest.java
+++ b/src/test/java/no/nibio/vips/util/weather/YrWeatherForecastProviderTest.java
@@ -87,26 +87,26 @@ public class YrWeatherForecastProviderTest {
             //weatherStation.setAltitude(110.0);
             //weatherStation.setLongitude(10.83474668);
             //weatherStation.setLatitude(59.63851390);
-            weatherStation.setAltitude(19.0);
-            weatherStation.setLongitude(6.57052);
-            weatherStation.setLatitude(60.2542);
+            weatherStation.setAltitude(102.0);
+            weatherStation.setLongitude(11.39);
+            weatherStation.setLatitude(59.388);
             result = instance.getWeatherForecasts(weatherStation);
             assertNotNull(result);
-            
-            /*for(WeatherObservation obs:result)
+            Collections.sort(result);
+            for(WeatherObservation obs:result)
             {
-                if(obs.getElementMeasurementTypeId().equals("RR"))
+                if(obs.getElementMeasurementTypeId().equals("TM"))
                 System.out.println(obs.toString());
-            }*/
+            }
             
             WeatherUtil wUtil = new WeatherUtil();
-            wUtil.checkForAndFixHourlyTimeSeriesHoles(result);
+            //wUtil.checkForAndFixHourlyTimeSeriesHoles(result);
             
             // Testing Jablanica (Republika Srpska, Bosnia)
             weatherStation.setAltitude(219.0);
             weatherStation.setLongitude(16.988056);
             weatherStation.setLatitude(45.115);
-            result = instance.getWeatherForecasts(weatherStation);
+            //result = instance.getWeatherForecasts(weatherStation);
             assertNotNull(result);
             
             /**Collections.sort(result);
-- 
GitLab