diff --git a/ipmd/static/ipmd/js/ipmdlib.js b/ipmd/static/ipmd/js/ipmdlib.js index 052fcdad141eedbf98fe91c68f18ffa7e4075315..1637f2bd54d6bd2f146e958834738dd15438ee9f 100644 --- a/ipmd/static/ipmd/js/ipmdlib.js +++ b/ipmd/static/ipmd/js/ipmdlib.js @@ -40,10 +40,27 @@ async function getModelInputSchema(dssId,modelId) { return await response.json(); } -async function getWeatherDatasource(weatherDatasourceId) +/*async function getWeatherDatasource(weatherDatasourceId) { const response = await fetch(ipmdWeatherApiURL + "rest/weatherdatasource/" + weatherDatasourceId); return await response.json(); +}*/ + +function getWeatherDatasourceEndpoint(weatherDatasource) +{ + return weatherDatasource.endpoint.replace("{WEATHER_API_URL}",ipmdWeatherApiURL); +} + +function getWeatherDatasource(weatherDatasources, weatherDatasourceId) +{ + for(let i=0;i<weatherDatasources.length;i++) + { + if(weatherDatasources[i].id == weatherDatasourceId) + { + return weatherDatasources[i]; + } + } + return null; } async function getWeatherDatasources() @@ -61,10 +78,22 @@ function getWeatherStationList(weatherDatasource){ let station = {"id":feature.id, "name":feature.properties.name} stationList.push(station); } + stationList.sort((a, b) => { + return (a.name < b.name) ? -1 : (a.name > b.name) ? 1 : 0; + }); return stationList; } -async function getLocationWeatherData(endpoint, weatherStationId, parameters, interval, dateStart, dateEnd){ +function renderWeatherStationSelectList(stationList, selectList){ + + selectList.add(new Option("-- Please select weather station --", -1)); + for(let i=0;i<stationList.length; i++) + { + selectList.add(new Option(stationList[i].name, stationList[i].id)); + } +} + +async function getStationWeatherData(endpoint, weatherStationId, parameters, interval, dateStart, dateEnd){ const response = await fetch(endpoint + "?timeStart=" + dateStart + "&timeEnd=" + dateEnd @@ -75,6 +104,18 @@ async function getLocationWeatherData(endpoint, weatherStationId, parameters, in return await response.json(); } +async function getLocationWeatherData(endpoint, longitude, latitude, parameters, interval, dateStart, dateEnd){ + const response = await fetch(endpoint + + "?timeStart=" + dateStart + + "&timeEnd=" + dateEnd + + "&interval=" + interval + + "&longitude=" + longitude + + "&latitude=" + latitude + + "¶meters=" + parameters.join(",") + ); + return await response.json(); +} + async function runModel(endpoint, inputData) { const response = await fetch(endpoint, { @@ -112,6 +153,71 @@ function getWeatherParameter(weatherParameterId){ return null; } +function getWeatherStationCoordinate(weatherDatasource, weatherStationId) +{ + geoJson = JSON.parse(weatherDatasource.spatial.geoJSON); + let stationList = []; + for(let i=0;i<geoJson.features.length;i++) + { + let feature = geoJson.features[i]; + if(feature.id == weatherStationId) + { + // TODO: Don't assume feature is a point + return feature.geometry.coordinates; + } + } + + return null; +} + +function getPragmaticWeatherParameterList(requestedParameters, availableParameters) +{ + let completeList = []; + for(let i=0;i<requestedParameters.length;i++) + { + completeList = completeList.concat(requestedParameters[i], fallbackParams[requestedParameters[i]]); + } + console.info(completeList); + console.info(availableParameters); + + return completeList.filter(param => availableParameters.includes(param)); + +} + +/** + * Merges the two datasets. Keeping the primaryData if both sets have data for same time and parameter + * If a merge operation is not possible, primaryData is returned unchanged + * @param {Object} primaryData + * @param {Object} secondaryData + */ +function mergeWeatherData(primaryData, secondaryData) +{ + + // Where to start in primaryData? + let mergedData = { + timeStart: null, + timeEnd: null, + interval: primaryData.interval, + weatherParameters: primaryData.weatherParameters + }; + + console.info("primaryData.timeStart=" + primaryData.timeStart); + console.info("mergedData.timeStart=" + mergedData.timeStart); + +} + +const fallbackParams = { + 1001: [1002], + 1002: [1001], + 3001: [3002], + 3002: [3001], + 4002: [4003,4012,4013], + 4003: [4002,4013,4012], + 4012: [4013,4002,4003], + 4013: [4012,4003,4002] +} + + const weatherParameterList = [ { "id": 1001, diff --git a/ipmd/templates/ipmd/saddlegallmidgeform.html b/ipmd/templates/ipmd/saddlegallmidgeform.html index 6692467ee82e1486e91f55893e0cf0e3874a8dee..3985488860c6b58433e160d386abc42510d64f8b 100644 --- a/ipmd/templates/ipmd/saddlegallmidgeform.html +++ b/ipmd/templates/ipmd/saddlegallmidgeform.html @@ -27,10 +27,32 @@ <div class="singleBlockContainer"> <h1>{% trans "Saddle gall midge" %}</h1> <div id="inputForm"></div> - <div id="weatherStations" style="display: none;"> - <h2>Weather stations</h2> - <select class="form-control" name="weatherStationId" id="weatherStationId"></select> - </div> + <div id="weatherDataForm" style="display: none;"> + <h2>Weather data</h2> + <fieldset id="historicDatasourceFields"> + <legend>Weather datasource (historic)</legend> + <select class="form-control" name="weatherDatasourceId" id="weatherDatasourceList" onchange="handleWeatherDatasourceSelected(this);"></select> + <div id="historicSourceInfo" style="display: none;"></div> + </fieldset> + <fieldset id="stationFields" style="display: none;"> + <select class="form-control" name="weatherStationId" id="weatherStationId" onchange="handleWeatherStationSelected(this);" ></select> + </fieldset> + <fieldset id="locationFields"> + <legend>Location (decimal degrees)</legend> + <div class="form-group"> + <label for="latitude">Latitude</label> + <input type="number" class="form-control" id="latitude" name="latitude" placeholder="Latitude"> + </div> + <div class="form-group"> + <label for="longitude">Longitude</label> + <input type="number" class="form-control" id="longitude" name="longitude" placeholder="Longitude"> + </div> + </fieldset> + <fieldset id="forecastDatasourceFields"> + <legend>Weather forecasts datasource</legend> + <select class="form-control" name="forecastWeatherDatasourceId" id="forecastWeatherDatasourceList" onchange="handleForecastSourceSelected(this);"></select> + <div id="forecastSourceInfo" style="display: none;"></div> + </fieldset> <button class="btn btn-primary" type="button" onclick="submitData();">Submit</button> <div style="aspect-ratio: 2;"> <canvas id="resultChart""></canvas> @@ -49,13 +71,14 @@ <script type="text/javascript"> // Page globals var modelMetaData = undefined; - var weatherDatasource = undefined; + var currentWeatherDatasource = undefined; + var currentForecastWeatherDatasource = undefined; var weatherDatasources = undefined; var editor = undefined; - const selectList = document.getElementById("weatherStationId"); - + var selectList = document.getElementById("weatherStationId"); + var weatherData = undefined; - + async function initPage() { modelMetaData = await getModelMetadata("adas.dss","HAPDMA"); document.getElementById("modelDescription").innerHTML= await modelMetaData["description"]; @@ -71,32 +94,90 @@ let fullSchema = JSON.parse(modelMetaData["execution"]["input_schema"]); if(fullSchema["properties"]["weatherData"] !== undefined) { - //console.info("Adding weather stations"); - // Pull weather stations from web service, render list - weatherDatasource = await getWeatherDatasource("no.nibio.lmt"); + + // Pull weather data sources from web service, render lists (historic and forecast sources, some are both) weatherDatasources = await getWeatherDatasources(); console.info(weatherDatasources); - stationList = await getWeatherStationList(weatherDatasource); - stationList.sort((a, b) => { + weatherDatasources.sort((a, b) => { return (a.name < b.name) ? -1 : (a.name > b.name) ? 1 : 0; }); - - for(let i=0;i<stationList.length; i++) + let weatherDatasourceList = document.getElementById("weatherDatasourceList"); + weatherDatasourceList.add(new Option("Please select a weather datasource", "-1")); + for(let i=0;i<weatherDatasources.length; i++) { - selectList.add(new Option(stationList[i].name, stationList[i].id)) + if(weatherDatasources[i].temporal.historic != null && weatherDatasources[i].temporal.historic.start != null) + { + weatherDatasourceList.add(new Option(weatherDatasources[i].name, weatherDatasources[i].id)); + } + } + let forecastWeatherDatasourceList = document.getElementById("forecastWeatherDatasourceList"); + forecastWeatherDatasourceList.add(new Option("Please select a weather forecast datasource", "-1")); + for(let i=0;i<weatherDatasources.length; i++) + { + if(weatherDatasources[i].temporal.forecast != null && weatherDatasources[i].temporal.forecast > 0) + { + forecastWeatherDatasourceList.add(new Option(weatherDatasources[i].name, weatherDatasources[i].id)); + } } - document.getElementById("weatherStations").style.display = "block"; - console.info(weatherDatasource); + + document.getElementById("weatherDataForm").style.display = "block"; + } // TODO: Remove this auto-mock!! editor.getValue().optionalData.startDate="2023-08-01"; editor.getValue().optionalData.endDate="2023-08-10"; - submitData(); + document.getElementById("longitude").value="11.781989"; + document.getElementById("latitude").value="59.680468"; + //submitData(); } initPage(); + function handleWeatherDatasourceSelected(weatherDatasourceList){ + currentWeatherDatasource = getWeatherDatasource( + weatherDatasources, + weatherDatasourceList.options[weatherDatasourceList.selectedIndex].value + ); + let sourceInfo = document.getElementById("historicSourceInfo"); + sourceInfo.innerHTML = currentWeatherDatasource.description; + sourceInfo.style.display="block"; + if(currentWeatherDatasource.access_type == "stations") + { + // Display the weather stations + renderWeatherStationSelectList(getWeatherStationList(currentWeatherDatasource), selectList); + document.getElementById("stationFields").style.display = "block"; + } + else + { + // Location based API + // Display lat/lon input fields + document.getElementById("stationFields").style.display = "none"; + // TODO: Add map? + + } + console.info(currentWeatherDatasource); + } + + function handleForecastSourceSelected(forecastSourceSelectList) + { + currentForecastWeatherDatasource = getWeatherDatasource( + weatherDatasources, + forecastWeatherDatasourceList.options[forecastWeatherDatasourceList.selectedIndex].value + ); + let sourceInfo = document.getElementById("historicSourceInfo"); + sourceInfo.innerHTML = currentWeatherDatasource.description; + sourceInfo.style.display="block"; + } + + function handleWeatherStationSelected(weatherStationSelectList) + { + let weatherStationId = selectList.options[selectList.selectedIndex].value; + let stationCoordinate = getWeatherStationCoordinate(currentWeatherDatasource, weatherStationId); + console.info(stationCoordinate); + document.getElementById("longitude").value = stationCoordinate[0]; + document.getElementById("latitude").value = stationCoordinate[1]; + } async function submitData(){ console.info("submitData!"); @@ -115,24 +196,92 @@ if(fullSchema["properties"]["weatherData"] !== undefined) { console.info("Need to get weather data!"); - let weatherStationId = selectList.options[selectList.selectedIndex].value; + let forecastData = undefined; + // 1. Historic weather data + if(currentWeatherDatasource != undefined) + { + if(currentWeatherDatasource.access_type == "stations") + { + let weatherStationId = selectList.options[selectList.selectedIndex].value; - weatherData = await getLocationWeatherData( - weatherDatasource.endpoint, - weatherStationId, - (function (){ - let parameterList = [] - modelMetaData.input.weather_parameters.forEach(function(weatherParameter){ - parameterList.push(weatherParameter.parameter_code) - }) - return parameterList; - }()), - 3600, - inputData.optionalData.startDate, - inputData.optionalData.endDate, - ); + weatherData = await getStationWeatherData( + getWeatherDatasourceEndpoint(currentWeatherDatasource), + weatherStationId, + getPragmaticWeatherParameterList( + function (){ + let parameterList = [] + modelMetaData.input.weather_parameters.forEach(function(weatherParameter){ + parameterList.push(weatherParameter.parameter_code) + }) + return parameterList; + }(), + currentWeatherDatasource.parameters.common + ), + 3600, + inputData.optionalData.startDate, + inputData.optionalData.endDate, + ); + + } + else + { + weatherData = await getLocationWeatherData( + getWeatherDatasourceEndpoint(currentWeatherDatasource), + document.getElementById("longitude").value, + document.getElementById("latitude").value, + getPragmaticWeatherParameterList( + function (){ + let parameterList = [] + modelMetaData.input.weather_parameters.forEach(function(weatherParameter){ + parameterList.push(weatherParameter.parameter_code) + }) + return parameterList; + }(), + currentWeatherDatasource.parameters.common + ), + 3600, + inputData.optionalData.startDate, + inputData.optionalData.endDate, + ); + } + } + + // 2. Forecast weather data + if(currentForecastWeatherDatasource != undefined) + { + forecastData = await getLocationWeatherData( + getWeatherDatasourceEndpoint(currentForecastWeatherDatasource), + document.getElementById("longitude").value, + document.getElementById("latitude").value, + getPragmaticWeatherParameterList( + function (){ + let parameterList = [] + modelMetaData.input.weather_parameters.forEach(function(weatherParameter){ + parameterList.push(weatherParameter.parameter_code) + }) + return parameterList; + }(), + currentForecastWeatherDatasource.parameters.common + ), + 3600, + inputData.optionalData.startDate, + inputData.optionalData.endDate, + ); + // Merge if both historic and forecast data have been collected + if(weatherData == undefined) + { + weatherData = forecastData; + } + else + { + // Is NULL until method is implemented + weatherData = mergeWeatherData(weatherData, forecastData); + } + } + + inputData["weatherData"] = weatherData; - console.info(weatherData); + //console.info(weatherData); } // Ready to call server? //console.info(JSON.stringify(inputData)); @@ -204,6 +353,8 @@ } + + // Mock result!!! Waiting for ADAS to fix CORS issue const mockResult={ "timeStart": "2023-06-30T22:00:00+00:00",