diff --git a/ipmd/static/ipmd/js/ipmdlib.js b/ipmd/static/ipmd/js/ipmdlib.js index 18931fead01e3d5e913bc79064b7e862ce0633f9..56415cf8398a4fb85e4c17b4b6c4be9c6b8dd017 100644 --- a/ipmd/static/ipmd/js/ipmdlib.js +++ b/ipmd/static/ipmd/js/ipmdlib.js @@ -23,7 +23,8 @@ */ const ipmdDSSApiURL = "https://platform.ipmdecisions.net/api/dss/"; -const ipmdWeatherApiURL = "https://platform.ipmdecisions.net/api/wx/"; +//const ipmdWeatherApiURL = "https://platform.ipmdecisions.net/api/wx/"; +const ipmdWeatherApiURL = "http://ipmdlocal/api/wx/"; async function getModelMetadata(dssId,modelId) { @@ -281,10 +282,10 @@ function mergeWeatherData(primaryData, secondaryData) }); // Calculate dimensions of the merged data set - console.info("timeStart=" + mergedData.timeStart.format("YYYY-MM-DD") + ", timeEnd=" + mergedData.timeEnd.format("YYYY-MM-DD")); + //console.info("timeStart=" + mergedData.timeStart.format("YYYY-MM-DD") + ", timeEnd=" + mergedData.timeEnd.format("YYYY-MM-DD")); let length = (getUnix(mergedData.timeEnd) - getUnix(mergedData.timeStart)) / mergedData.interval; let width = mergedData.weatherParameters.length; - console.info(length + "*" + width); + //console.info(length + "*" + width); let dataSet = Array.from(Array(length), () => new Array(width)); //console.info(dataSet); // Keeping track of offsets (if dataset does not cover the entire combined period of the merging datasets) @@ -293,8 +294,8 @@ function mergeWeatherData(primaryData, secondaryData) let primaryDataSet = primaryData.locationWeatherData[0].data; let secondaryDataSet = secondaryData.locationWeatherData[0].data; - console.info("primaryOffset=" + primaryOffset); - console.info(primaryDataSet); + //console.info("primaryOffset=" + primaryOffset); + //console.info(primaryDataSet); // Finally: Merge the data! for(let i=0;i<dataSet.length;i++) @@ -312,7 +313,7 @@ function mergeWeatherData(primaryData, secondaryData) } if(primaryOffset <= i && i-primaryOffset < primaryDataSet.length ) { - console.info(i-primaryOffset); + //console.info(i-primaryOffset); for(let j=0; j<primaryData.weatherParameters.length;j++) { if(!isEmpty(primaryDataSet[i-primaryOffset][j])) @@ -331,7 +332,7 @@ function mergeWeatherData(primaryData, secondaryData) function getUnix(aDate) { - console.info(typeof aDate); + //console.info(typeof aDate); if(typeof aDate == "string") { aDate = moment(aDate); @@ -345,6 +346,111 @@ function isEmpty(val) return val === undefined || val === null; } +// Keeping track of maps +var maps = {}; +/** + * Requires OpenLayers v. 4 + * @param {String} containerId + * @param {JSON} geoJson + */ +async function initDataSourceMap(containerId, geoJson, countryCodeList) +{ + + let map = maps[containerId]; + // Delete whatever was there + if(map != undefined) + { + map.setTarget(undefined); + map = null; + } + + //console.info(countryCodeList); + //console.info(geoJson); + // If no data, no map + if((geoJson == undefined || geoJson == null || geoJson.features.length == 0) && (countryCodeList == null || countryCodeList.length == 0)) + { + document.getElementById(containerId).innerHTML = "NO GEODATA PROVIDED BY WEATHER DATA SOURCE"; + return; + } + else{ + document.getElementById(containerId).innerHTML = ""; + } + + + var backgroundLayer = new ol.layer.Tile({ + source: new ol.source.OSM({ + attributions: [ + new ol.Attribution({ + html: "TEST" + }) + ] + }) + }); + + // Creating the map + map = new ol.Map({ + target: containerId, + layers: [backgroundLayer], + renderer: 'canvas' + }); + maps[containerId] = map; + // Setting zoom and center for the map (need to do this after creating map. so that we can transform our + // center to correct map projection) + var view = new ol.View({ + center: ol.proj.transform([10,65], 'EPSG:4326', map.getView().getProjection().getCode()), + zoom: 7, + maxZoom: 7 + }); + map.setView(view); + + 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, { + dataProjection: 'EPSG:4326', + featureProjection: map.getView().getProjection().getCode() + }); + } + + if(drawnFeatures != undefined) + { + + //console.info(drawnFeatures); + var featureOverlay = new ol.layer.Vector({ + source: new ol.source.Vector({ + features: features + }) + }); + featureOverlay.getSource().addFeatures(drawnFeatures); + featureOverlay.setMap(map); + + extent = featureOverlay.getSource().getExtent(); + map.getView().fit(extent, map.getSize()); + } +} + +async function getCountryBoundaries(countryCodeList) +{ + const response = await fetch(ipmdWeatherApiURL + "rest/country/" + countryCodeList.join(",")); + return await response.json(); +} + + const fallbackParams = { 1001: [1002], 1002: [1001], @@ -357,6 +463,7 @@ const fallbackParams = { } + const weatherParameterList = [ { "id": 1001, diff --git a/ipmd/templates/ipmd/saddlegallmidgeform.html b/ipmd/templates/ipmd/saddlegallmidgeform.html index 8aeb80df65181e964fd846f69d1476154ad4d5cc..4b7f065e35063d6571f4a24b19864c164292fb74 100644 --- a/ipmd/templates/ipmd/saddlegallmidgeform.html +++ b/ipmd/templates/ipmd/saddlegallmidgeform.html @@ -29,30 +29,48 @@ <div id="inputForm"></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 class="row"> + <div class="col-md-8"> + <fieldset id="historicDatasourceFields"> + <legend>Weather datasource (historic)</legend> + <select class="form-control" name="weatherDatasourceId" id="weatherDatasourceList" onchange="handleWeatherDatasourceSelected(this);"></select> + <div id="historicSourceInfoPanel" class="panel panel-default" style="display: none;"> + <div class="panel-body" id="historicSourceInfo"></div> + </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> </div> - <div class="form-group"> - <label for="longitude">Longitude</label> - <input type="number" class="form-control" id="longitude" name="longitude" placeholder="Longitude"> + <div class="col-md-4"> + <div id="historicDatasourceMap"></div> </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> + </div> + <div class="row"> + <div class="col-md-8"> + <fieldset id="forecastDatasourceFields"> + <legend>Weather forecasts datasource</legend> + <select class="form-control" name="forecastWeatherDatasourceId" id="forecastWeatherDatasourceList" onchange="handleForecastSourceSelected(this);"></select> + <div id="forecastSourceInfoPanel" class="panel panel-default" style="display: none;"> + <div class="panel-body" id="forecastSourceInfo"></div> + </div> + </fieldset> + </div> + <div class="col-md-4"> + <div id="forecastDatasourceMap"></div> + </div> + </div> <button class="btn btn-primary" type="button" onclick="submitData();">Submit</button> <div style="aspect-ratio: 2;"> <canvas id="resultChart""></canvas> @@ -62,11 +80,17 @@ <pre id="modelDescription"></pre> </div> {% endblock %} + +{% block extendCSS %} +<link rel="stylesheet" href="{% static "css/3rdparty/ol.css" %}" type="text/css"> +{% endblock%} + {% 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 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> <script type="text/javascript" src="{% static "ipmd/js/ipmdlib.js" %}"></script> <script type="text/javascript"> // Page globals @@ -83,13 +107,19 @@ modelMetaData = await getModelMetadata("adas.dss","HAPDMA"); document.getElementById("modelDescription").innerHTML= await modelMetaData["description"]; inputFormSchema = await getModelInputSchema("adas.dss","HAPDMA") - console.info(inputFormSchema); + inputFormSchema.title ="Model input data"; + //console.info(inputFormSchema); const element = document.getElementById('inputForm'); + + // See https://github.com/json-editor/json-editor#options editor = new JSONEditor(element, { ajax: true, schema: inputFormSchema, - theme: "bootstrap4" + theme: "bootstrap4", + iconlib: "fontawesome4", + disable_edit_json: true, + disable_properties: true }); let fullSchema = JSON.parse(modelMetaData["execution"]["input_schema"]); if(fullSchema["properties"]["weatherData"] !== undefined) @@ -97,7 +127,7 @@ // Pull weather data sources from web service, render lists (historic and forecast sources, some are both) weatherDatasources = await getWeatherDatasources(); - console.info(weatherDatasources); + //console.info(weatherDatasources); weatherDatasources.sort((a, b) => { return (a.name < b.name) ? -1 : (a.name > b.name) ? 1 : 0; }); @@ -138,10 +168,21 @@ currentWeatherDatasource = getWeatherDatasource( weatherDatasources, weatherDatasourceList.options[weatherDatasourceList.selectedIndex].value - ); + ); + let sourceInfoPanel = document.getElementById("historicSourceInfoPanel"); + if(currentWeatherDatasource == null) + { + sourceInfoPanel.style.display="none"; + document.getElementById("stationFields").style.display = "none"; + return; + } + + // Display map + initDataSourceMap("historicDatasourceMap", JSON.parse(currentWeatherDatasource.spatial.geoJSON), currentWeatherDatasource.spatial.countries); + let sourceInfo = document.getElementById("historicSourceInfo"); sourceInfo.innerHTML = currentWeatherDatasource.description; - sourceInfo.style.display="block"; + sourceInfoPanel.style.display="block"; if(currentWeatherDatasource.access_type == "stations") { // Display the weather stations @@ -156,7 +197,7 @@ // TODO: Add map? } - console.info(currentWeatherDatasource); + //console.info(currentWeatherDatasource); } function handleForecastSourceSelected(forecastSourceSelectList) @@ -164,29 +205,36 @@ currentForecastWeatherDatasource = getWeatherDatasource( weatherDatasources, forecastWeatherDatasourceList.options[forecastWeatherDatasourceList.selectedIndex].value - ); - let sourceInfo = document.getElementById("historicSourceInfo"); - sourceInfo.innerHTML = currentWeatherDatasource.description; - sourceInfo.style.display="block"; + ); + + let sourceInfoPanel = document.getElementById("forecastSourceInfoPanel"); + if(currentForecastWeatherDatasource == null) + { + sourceInfoPanel.style.display="none"; + return; + } + let sourceInfo = document.getElementById("forecastSourceInfo"); + sourceInfo.innerHTML = currentForecastWeatherDatasource.description; + sourceInfoPanel.style.display="block"; } function handleWeatherStationSelected(weatherStationSelectList) { let weatherStationId = selectList.options[selectList.selectedIndex].value; let stationCoordinate = getWeatherStationCoordinate(currentWeatherDatasource, weatherStationId); - console.info(stationCoordinate); + //console.info(stationCoordinate); document.getElementById("longitude").value = stationCoordinate[0]; document.getElementById("latitude").value = stationCoordinate[1]; } async function submitData(){ - console.info("submitData!"); + //console.info("submitData!"); let inputData = editor.getValue(); - console.info(inputData); + //console.info(inputData); // Add hidden parameters let fullSchema = JSON.parse(modelMetaData["execution"]["input_schema"]); const hiddenParameters = modelMetaData["execution"]["input_schema_categories"]["hidden"]; - console.info(hiddenParameters); + //console.info(hiddenParameters); for(let i=0;i<hiddenParameters.length;i++) { let hiddenParameter = hiddenParameters[i]; @@ -195,7 +243,7 @@ // Check for weatherData element. Assuming it's at the root node if(fullSchema["properties"]["weatherData"] !== undefined) { - console.info("Need to get weather data!"); + //console.info("Need to get weather data!"); let forecastData = undefined; // 1. Historic weather data if(currentWeatherDatasource != undefined) @@ -296,7 +344,7 @@ let chartData = []; // Generate dates let dates = getDateArray(result.timeStart, 86400, result.locationResult[0].length); - console.info(dates); + //console.info(dates); for(let i=0; i< result.resultParameters.length;i++) { let dataset = {label:result.resultParameters[i], data: []} @@ -341,7 +389,8 @@ html += "</tr></thead>" html += "<tbody>"; - for(let i=0;i< weatherData.locationWeatherData[0].length;i++) + //console.info("Weatherdata length: " + weatherData.locationWeatherData[0].data.length); + for(let i=0;i< weatherData.locationWeatherData[0].data.length;i++) { html += "<tr><td>" + moment(dates[i]).format("YYYY-MM-DD HH:mm:ss") + "</td>"; weatherData.locationWeatherData[0].data[i].forEach(function(val){html+="<td>" + val + "</td>";})