From f9cd1292a0c387f6a357e8d5b8ae106c61e26382 Mon Sep 17 00:00:00 2001
From: Tor-Einar Skog <tor-einar.skog@nibio.no>
Date: Tue, 5 Sep 2023 15:06:05 +0200
Subject: [PATCH] Check that selected point is covered by forecast source

---
 ipmd/static/ipmd/js/ipmdlib.js               | 64 ++++++++++++++------
 ipmd/templates/ipmd/saddlegallmidgeform.html | 22 +++++--
 2 files changed, 64 insertions(+), 22 deletions(-)

diff --git a/ipmd/static/ipmd/js/ipmdlib.js b/ipmd/static/ipmd/js/ipmdlib.js
index 791bc4fe..c270c275 100644
--- a/ipmd/static/ipmd/js/ipmdlib.js
+++ b/ipmd/static/ipmd/js/ipmdlib.js
@@ -20,6 +20,7 @@
 /**
  * Dependencies:
  * momentJS
+ * turf v6
  */
 
 const ipmdDSSApiURL = "https://platform.ipmdecisions.net/api/dss/";
@@ -190,6 +191,26 @@ function getPragmaticWeatherParameterList(requestedParameters, availableParamete
 
 }
 
+async function isPointCoveredByDatasource(coordinate, datasource)
+{
+    // If the geoJson is {"type":"Sphere"}, return true
+    if(datasource.spatial.geoJSON != null && JSON.parse(datasource.spatial.geoJSON).type.toLowerCase() == "sphere")
+    {
+        return true;
+    }
+    let geoJson = await getDatasourceFeatures(JSON.parse(datasource.spatial.geoJSON), datasource.spatial.countries);
+    console.info(geoJson);
+    let retVal = false;
+    for(let i=0; i<geoJson.features.length;i++)
+    {
+        if(turf.booleanPointInPolygon(coordinate, geoJson.features[i]))
+        {
+            retVal = true;
+        }
+    }
+    return retVal;
+}
+
 /**
      * Displays the weather data in a table
      * @param {Object} weatherData the weatherData in IPM Decisions format
@@ -439,25 +460,14 @@ async function initDataSourceMap(containerId, geoJson, countryCodeList, featureC
 	let features = new ol.Collection();
     
     let format = new ol.format.GeoJSON();
-    let drawnFeatures = undefined;
-
-    // If we have geoJson available, we display that
-    if (geoJson != null && geoJson.features.length > 0) {
-        drawnFeatures = format.readFeatures(geoJson, {
-            dataProjection: 'EPSG:4326',
-            featureProjection: map.getView().getProjection().getCode()
-        });
-       
-    }
-    // If not, we have to get geoJson mapped to the countries
-    else if (countryCodeList != undefined && countryCodeList != null && countryCodeList.length > 0) {
-        let countryBoundaries = await getCountryBoundaries(countryCodeList);
-        //console.info(countryBoundaries);
-        drawnFeatures = await format.readFeatures(countryBoundaries, {
+    
+    let drawnFeatures = await format.readFeatures(
+        await getDatasourceFeatures(geoJson, countryCodeList), 
+        {
             dataProjection: 'EPSG:4326',
             featureProjection: map.getView().getProjection().getCode()
-        });
-    }
+        }
+    );
 
     let featureOverlay = undefined;
 
@@ -513,6 +523,22 @@ async function initDataSourceMap(containerId, geoJson, countryCodeList, featureC
     });
 }
 
+/**
+ * Get "complete" spatial info from weather datasource, using either provided GeoJson or 
+ * inferred from list of countries
+ * @param {JSON} geoJson 
+ * @param {Array} countryCodeList 
+ * @returns {Json} GeoJson
+ */
+async function getDatasourceFeatures(geoJson, countryCodeList)
+{
+    // If we have geoJson available, we display that
+    if (geoJson != null && geoJson.features !== undefined && geoJson.features.length > 0) {
+        return geoJson;
+    }
+    return await getCountryBoundaries(countryCodeList);
+}
+
 const styleUnselected = new ol.style.Style({
     fill: new ol.style.Fill({
         color: 'rgba(173, 173, 173, 0.5)'
@@ -580,6 +606,10 @@ function getDecimalDegrees(map, coordinate)
 
 async function getCountryBoundaries(countryCodeList)
 {
+    if(countryCodeList == undefined || countryCodeList == null)
+    {
+        return {};
+    }
     const response = await fetch(ipmdWeatherApiURL + "rest/country/" + countryCodeList.join(","));
     return await response.json();
 }
diff --git a/ipmd/templates/ipmd/saddlegallmidgeform.html b/ipmd/templates/ipmd/saddlegallmidgeform.html
index 33f20917..8252456c 100644
--- a/ipmd/templates/ipmd/saddlegallmidgeform.html
+++ b/ipmd/templates/ipmd/saddlegallmidgeform.html
@@ -84,6 +84,7 @@
 {% block customJS %}
 <script src="https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.min.js"></script>
 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
+<script src='https://unpkg.com/@turf/turf@6/turf.min.js'></script>
 <script type="text/javascript" src="{% static "js/3rdparty/moment.min.js" %}"></script>
 <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment"></script>
 <script type="text/javascript" src="{% static "js/3rdparty/ol-debug.js" %}"></script>
@@ -203,7 +204,6 @@
 
     function handleHistoricDatasourceMapClicked(id, coordinate)
     {
-
         //console.info("Map clicked, station=" + id +", coordinate=" + coordinate + ". TODO: select station and/or populate lat/lon fields");
         // id != null => station. Pull station coordinates from source list to avoid inacurracies in user's map click coordinate
         if(id !== null)
@@ -234,7 +234,7 @@
         }
     }
 
-    function handleForecastSourceSelected(forecastSourceSelectList)
+    async function handleForecastSourceSelected(forecastSourceSelectList)
     {
         currentForecastWeatherDatasource = getWeatherDatasource(
             weatherDatasources, 
@@ -248,10 +248,12 @@
             return;
         }
 
+        let geoJson = JSON.parse(currentForecastWeatherDatasource.spatial.geoJSON);
+
         // Display map
         initDataSourceMap(
             "forecastDatasourceMap", 
-            JSON.parse(currentForecastWeatherDatasource.spatial.geoJSON), 
+            geoJson, 
             currentForecastWeatherDatasource.spatial.countries, 
             function(){} // We don't do nothing, right?
             );
@@ -259,6 +261,10 @@
         let sourceInfo = document.getElementById("forecastSourceInfo");
         sourceInfo.innerHTML = currentForecastWeatherDatasource.description;
         sourceInfoPanel.style.display="block";
+        // Does the forecast data source cover the point in question?
+        console.info("In geoJson? " + await isPointCoveredByDatasource(getLatLon(), currentForecastWeatherDatasource));
+        
+
     }
 
     function handleWeatherStationSelected(weatherStationSelectList)
@@ -275,6 +281,11 @@
         document.getElementById("latitude").value = coordinate[1];
     }
 
+    function getLatLon()
+    {
+        return [document.getElementById("longitude").value, document.getElementById("latitude").value];
+    }
+
     async function submitData(){
         //console.info("submitData!");
         let inputData = editor.getValue();
@@ -321,10 +332,11 @@
                 }
                 else
                 {
+                    coordinate = getLatLon();
                     weatherData = await getLocationWeatherData(
                         getWeatherDatasourceEndpoint(currentWeatherDatasource),
-                        document.getElementById("longitude").value,
-                        document.getElementById("latitude").value,
+                        coordinate[0],
+                        coordinate[1],
                         getPragmaticWeatherParameterList(
                             function (){
                                 let parameterList = []
-- 
GitLab