diff --git a/fusarium/static/fusarium/js/oatFloweringModelForm.js b/fusarium/static/fusarium/js/oatFloweringModelForm.js index dd1848ddfd2b575213121ad8a277b18151cc555a..19201d2d8fb39bad4902afb190430580f6042393 100755 --- a/fusarium/static/fusarium/js/oatFloweringModelForm.js +++ b/fusarium/static/fusarium/js/oatFloweringModelForm.js @@ -49,7 +49,8 @@ var corePass = ""; // Location of weather data source var weatherDataBaseUri = "https://lmt.nibio.no/agrometbase/export/getSeasonDailyTemperaturesJSON.php"; //var weatherDataBaseUri = "http://agrometbase-local/agrometbase/export/getSeasonDailyTemperaturesJSON.php"; - +const LMTServicesBaseUri = "http://localhost:8081/"; +const OpenMeteoBaseUri = "http://localhost:8082/rest/grid/openmeteo/" // Setting current time var now = moment(); //var now = moment("2014-07-04"); @@ -76,7 +77,6 @@ var oatForm = function(options) var height = workspace.offsetHeight; var width = workspace.offsetWidth; DEBUG = options.debug != null ? options.debug : false; - workspace.innerHTML = getStartHTML(); var theForm = document.getElementById(theFormId); @@ -105,13 +105,13 @@ var submitForm = function() var dateOfSowingLimit = moment(thisYear + "-07-01","YYYY-MM-DD"); if(getDateOfSowing().isAfter(dateOfSowingLimit)) { - alert("Saadato er etter 1. juli. Dette stemmer antakelig ikke. Ingen beregninger er foretatt."); + alert("Sådato er etter 1. juli. Dette stemmer antakelig ikke. Ingen beregninger er foretatt."); return; } var theForm = document.getElementById(theFormId); if(theForm.weatherStationId.options[theForm.weatherStationId.selectedIndex].value <= 0) { - alert("Vennligst velg klimastasjon"); + alert("Vennligst velg værstasjon"); return; } // See if test input now is set @@ -131,15 +131,15 @@ var submitForm = function() /** * Creates the configuration for weather data. Moves on to running model after data returns */ -var createConfig = function(){ +const createConfig = async function(){ - var theForm = document.getElementById(theFormId); - var weatherStationId = theForm.weatherStationId.options[theForm.weatherStationId.selectedIndex].value; - var dateOfSowing = getDateOfSowing(); - // Challenge: Get weather data asynchronously first.... - //Tip: Use jQuery.stringify - //var theConfig = + const theForm = document.getElementById(theFormId); + const weatherStationId = theForm.weatherStationId.options[theForm.weatherStationId.selectedIndex].value; + const dateOfSowing = getDateOfSowing(); + const endDateNormalData = moment(dateOfSowing).month(9).date(1); + + var weatherDataUri = [weatherDataBaseUri, "?startDate=", dateOfSowing.format("YYYY-MM-DD"), (DEBUG ? "&today=" + now.format("YYYY-MM-DD") : ""), @@ -147,25 +147,46 @@ var createConfig = function(){ //"&callback=?" ].join(""); + const normalDataRequestUri = LMTServicesBaseUri + "rest/vips/getdata/grovfornormal?" + + "elementMeasurementTypes[]=TM&timeZone=Europe/Oslo" + + "&weatherStationId=" + weatherStationId + + "&startDate=" + now.format("YYYY-MM-DD") + + "&endDate=" + now.year() + "-12-31" - var jqxhr = $.ajax( { - url:weatherDataUri, - dataType: "json" - }) - .done(function(data, textStatus, jqXHR) { - runModel(data); - }) - .fail(function( jqXHR, textStatus,errorThrown ) { - alert( "Request failed: " + errorThrown ); - }) - .always(function() { - //alert( "complete" ); - }) - ; - + const normalDataResponse = await fetch(normalDataRequestUri); + const normalData = await normalDataResponse.json(); + normalData.sort(sortWeatherData) + + const measuredDataRequestUri = document.getElementById("coordinates").checked? + OpenMeteoBaseUri + "?longitude=" + document.getElementById("longitude").value + + "&elementMeasurementTypes[]=TM&logIntervalId=2" + + "&latitude=" + document.getElementById("latitude").value + + "&timeZone=Europe/Oslo" + + "&startDate=" + dateOfSowing.format("YYYY-MM-DD") + + "&endDate=" + moment(now).add(10, "days").format("YYYY-MM-DD") + : LMTServicesBaseUri + "rest/vips/getdata/forecastfallback?"+ + "elementMeasurementTypes[]=TM&logInterval=1d" + + "&weatherStationId=" + weatherStationId + + "&timeZone=Europe/Oslo" + + "&startDate=" + dateOfSowing.format("YYYY-MM-DD") + "&startTime=00" + + "&endDate=" + moment(now).add(10, "days").format("YYYY-MM-DD") + "&endTime=00"; + + const measuredDataResponse = await fetch(measuredDataRequestUri); + const measuredData = await measuredDataResponse.json(); + // Combine data. Normal data has lowest priority + measuredData.sort(sortWeatherData); + const maxMeasuredDataTimestamp = moment(measuredData[measuredData.length-1].timeMeasured); + const mergedData = measuredData.concat(normalData.filter((obs) => moment(obs.timeMeasured) > maxMeasuredDataTimestamp)); + console.info(mergedData); + runModel(mergedData); }; +function sortWeatherData(a,b) +{ + return moment(a.timeMeasured).unix()- moment(b.timeMeasured).unix() ; +} + /** * Calls the oatFlowering model in the VIPS forecasting system * Displays data when results are returned @@ -231,6 +252,7 @@ var displayResults = function(data) console.log("z62.5: " + getDateForZ(data,62.5).tz("Europe/Oslo").format()); console.log("z69: " + dateZ69.tz("Europe/Oslo").format()); */ + // Scenario 1: Today is before date of sowing if(now.isBefore(dateOfSowing)) { @@ -312,6 +334,7 @@ var getRawResults = function(data) */ var displayResultHTML = function(text) { + document.getElementById("oatFloweringModelResults").style.display="block"; document.getElementById("oatFloweringModelResults").innerHTML= "<h2>Resultater</h2>" + text; } @@ -372,34 +395,6 @@ function getNow() else return now; } -/** - * @return the HTML for the form - */ -var getStartHTML = function() -{ - return [ - - "<form id='",theFormId,"' role='form'>", - "<div class='form-group'>", - "<label for='dateOfSowing'>Sådato</label> ", - "<input class='form-control' type='date' name='dateOfSowing', value='",now.format("YYYY-MM-DD"),"'/><br/>", - "</div>", - (DEBUG ? "<div class='form-group'>" :""), - (DEBUG ? "<label for='now'>Nådato </label>" : ""), - (DEBUG ? ["<input type='date' class='form-control' name='now' value='", now.format("YYYY-MM-DD"), "'/><br/>"].join("") : ""), - (DEBUG ? "</div>" :""), - "<div class='form-group'>", - "<label for='weatherStationId'>Målestasjon</label>", - "<select name='weatherStationId' class='form-control'>", - "<option value='-1'>-- Vennligst velg værstasjon --</option>", - "</select><br/>", - "<button type='button' class='btn btn-default' onclick='submitForm();'>Beregn</button>", - "</form>", - "</div>", - "<div id='resultsTable'></div>" - ].join(""); -} - var getAllValues = function(allValuesStr) { return JSON.parse(allValuesStr); diff --git a/fusarium/templates/fusarium/oat_flowering.html b/fusarium/templates/fusarium/oat_flowering.html index 7bdd13a872021d678f227be96d45e61105317b8d..bad1d5392fd0e5c6f7b117da500e27ce6995c3ce 100755 --- a/fusarium/templates/fusarium/oat_flowering.html +++ b/fusarium/templates/fusarium/oat_flowering.html @@ -3,17 +3,72 @@ {% block title%}{% trans "Oat flowering model" %}{%endblock%} {% block extendCSS %} +{% endblock %} +{% block customCSS %} +<link type="text/css" rel="stylesheet" href="{{VIPSLOGIC_URL}}/css/3rdparty/leaflet.css" /> +<link type="text/css" rel="stylesheet" href="{{VIPSLOGIC_URL}}/css/mapModal.css" /> {% endblock %} {% block content %} <div class="singleBlockContainer"> - <h1>{% trans "Oat flowering model" %}</h1> - <p> - Her kan du beregne tidspunkt for når havren er i blomst og dermed når - en eventuell behandling med soppmiddel mot Fusarium må utføres. - <a href='/forecasts/models/OATFLOWERM/' target='new'>Les mer</a> - </p> - <div id="oatFloweringModelForm" style="width:100%"></div> - <div id="oatFloweringModelResults" style="width:100%;"></div> + <div class="row"> + <div class="col-md-12"> + <h1>{% trans "Oat flowering model" %}</h1> + <p> + Her kan du beregne tidspunkt for når havren er i blomst og dermed når + en eventuell behandling med soppmiddel mot Fusarium må utføres. + <a href='/forecasts/models/OATFLOWERM/' target='new'>Les mer</a> + </p> + </div> + </div> + <div class="row"> + <div class="col-md-6"> + <div id="oatFloweringModelForm" style="width:100%"> + <form id="_oatFloweringModelForm" role="form"> + <div class="form-group"> + <label for="dateOfSowing">Sådato</label> + <input class="form-control" type="date" name="dateOfSowing" value=""><br> + </div> + <div class="form-group"> + <h4>Værdata og prognoser</h4> + <p>For værdata fram til nå og prognoser de nærmeste dagene fram i tid vil jeg bruke</p> + <div class="radio"> + <div class="radio"> + <label> + <input type="radio" name="weatherdataType" id="weatherstation" value="weatherstation" onchange="hideCoordinatesInput();storeUserSettings();"> + data fra værstasjonen valgt for normaldata (se nedenfor) + </label> + </div> + <label> + <input type="radio" name="weatherdataType" id="coordinates" value="coordinates" onchange="displayCoordinatesInput();storeUserSettings();"> + data for et spesifikt punkt (koordinater) + </label> + <div id="input-coordinates" class="form-inline" style="margin-top: 10px; display: none;"> + <input type="hidden" class="form-control" name="latitude" id="latitude" placeholder="Breddegrad" aria-label="Breddegrad"> + <input type="hidden" class="form-control" name="longitude" id="longitude" placeholder="Lengdegrad" aria-label="Lengdegrad"> + <input type="hidden" class="form-control" name="timezone" id="timezone" placeholder="Tidssone" aria-label="Tidssone"> + <div id="gridPointInfo"></div> + <button type="button" class="btn btn-primary" onclick="openCoordinatesMap();" style="margin-left: 5px;"><i class="fa fa-map-marker fa-lg"></i> {% trans "Select in map" %}</button> + </div> + <div id="coordinates-map" class="map-modal"></div> + </div> + </div> + <div class="form-group form-inline"> + <h4>Normaldata</h4> + <p>Brukes for beregning når tidspunkt for blomstring er senere enn 10 dager fram i tid</p> + <select name="weatherStationId" id="weatherStationId" class="form-control"> + <option value="-1">-- Vennligst velg værstasjon --</option> + </select><button type="button" class="btn btn-primary" onclick="openPoiMap()" style="margin-left: 5px;"><i class="fa fa-map-marker fa-lg"></i> {% trans "Select in map" %}</button> + + </div> + <div class="form-group"> + <button type="button" class="btn btn-primary" onclick="submitForm();">Beregn</button> + </div> + <div id="poi-map" class="map-modal"></div> + </form> + </div> + </div> + </div> + <div id="oatFloweringModelResults" class="alert alert-info" role="alert" style="width:100%; display: none;"></div> </div> {% endblock %} {% block extendJS%} @@ -25,18 +80,47 @@ <script type="text/javascript" src="{% static "js/3rdparty/modernizr_custom.js"%}"></script> <script type="text/javascript" src="{% url "javascript-catalog" %}"></script> <script type="text/javascript" src="{% url "views.settings_js" %}"></script> +<script type="text/javascript" src="{% static "js/util.js" %}"></script> + <script src="{% static "fusarium/js/oatFloweringModelForm.js" %}"></script> -<script type="text/javascript"> - - var weatherStations = []; - $(document).ready(function() { - $.getJSON("https://lmt.nibio.no/agrometbase/export/getNormalDataStationsJSON.php", function( json ) { +<script type="module"> + import MapModal from "{{VIPSLOGIC_URL}}/js/mapModal.js"; + const userSettingsFields = ["latitude","longitude","timezone","weatherStationId","heatSumStartDate"]; + const userSettingsRadios = ["weatherdataType"]; + window.weatherStations = []; + window.weatherStationIds = []; + + const inputLatitudeElement = document.getElementById("latitude"); + const inputLongitudeElement = document.getElementById("longitude"); + const selectWeatherstationElement = document.getElementById("weatherStationId"); + window.onload = (event) => { + + let userSettings = getLocalSettings(getNameSpaced("{{ form_id }}",userSettingsFields.concat(userSettingsRadios)), false); + + + // Settings found, render form and run model + if(Object.keys(userSettings).length > 0) + { + console.info("User settings found, configuring form!"); + } + + fetch("https://lmt.nibio.no/agrometbase/export/getNormalDataStationsJSON.php") + .then(response => { + if(response.ok) + { + return response.json(); + } + else + { + throw new Error("Network response was not OK." + response.text); + } + }) + .then(json => { weatherStations = json; + weatherStationIds = weatherStations.map(ws=>ws.weatherstation_id); var formApp = new oatForm({ target:"oatFloweringModelForm", weatherStations:weatherStations, - //coreUsername:"gamlevips", - //corePass:"gamlevips123" coreUsername:"testuser", corePass:"testpass", debug : false @@ -47,6 +131,125 @@ $('input[type=date]').datepicker({ dateFormat: 'yy-mm-dd' }); } }); - }); + }; + + window.displayCoordinatesInput = function () { + document.getElementById("coordinates").checked = true; + document.getElementById('input-coordinates').style.display="block"; + } + + window.hideCoordinatesInput = function () { + document.getElementById("weatherstation").checked = true; + document.getElementById('input-coordinates').style.display="none"; + } + + // Callback for coordinates map + function selectCoordinates(coordinatesData) { + const selectedLatitude = coordinatesData ? coordinatesData.latitude : undefined; + const selectedLongitude = coordinatesData ? coordinatesData.longitude : undefined; + + if(selectedLatitude && selectedLongitude) { + inputLatitudeElement.value = selectedLatitude; + inputLongitudeElement.value = selectedLongitude; + } + getTimezoneForPoint(selectedLatitude, selectedLongitude); + storeUserSettings(); + } + + const getTimezoneForPoint = (latitude, longitude) => { + getLocationInformation(latitude, longitude).then(locationInfo => { + document.getElementById("timezone").value = locationInfo.timezone; + document.getElementById("gridPointInfo").innerHTML = `<b>{% trans "Location name" %}</b> ${locationInfo.location}<br> + <b>{% trans "Latitude" %}</b> ${locationInfo.latitude}<br> + <b>{% trans "Longitude" %}</b> ${locationInfo.longitude}<br> + <b>{% trans "Timezone" %}</b> ${locationInfo.timezone}` + }); + } + + let selectedPoint = null; + let selectedFeature = undefined; + window.openCoordinatesMap = () => { + if (inputLatitudeElement.value && inputLongitudeElement.value) { + selectedPoint = 1; + selectedFeature = { + "type": "FeatureCollection", "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [parseFloat(inputLongitudeElement.value), parseFloat(inputLatitudeElement.value)] + }, + "properties": { + "pointOfInterestId": selectedPoint, + } + }] + }; + } else { + selectedPoint = undefined; + selectedFeature = undefined; + } + + + const isPoiMap = false; // Map should enable selection of coordinates (not pois) + const allowNewPoints = true; // User should be able to select new pois + const coordinatesMapInstance = new MapModal('coordinates-map', selectedFeature, 'nb', isPoiMap, allowNewPoints, selectCoordinates); + coordinatesMapInstance.openModal(selectedPoint); + } + + function selectPoi(poiData) + { + const selectedId = poiData ? poiData.pointOfInterestId : undefined; + if (selectedId) { + const optionIndex = Array.from(selectWeatherstationElement.options).findIndex(option => option.value == selectedId); + if (optionIndex !== -1) { + selectWeatherstationElement.selectedIndex = optionIndex; + } + } + } + + window.openPoiMap = () => { + fetch("https://lmt.nibio.no/services/rest/weatherstation/ipmdecisions", { + method: 'GET' + }) + .then(response => response.json()) + .then(geoJson => { + // FILTER with only Ids from the Cydia station list + let filteredFeatures = geoJson["features"] + .filter(feature => window.weatherStationIds.indexOf(feature.id) >= 0) + .map(feature => { + feature["properties"]["pointOfInterestName"] = feature["properties"]["name"]; + feature["properties"]["pointOfInterestId"] = feature["id"]; + feature["properties"]["pointOfInterestTypeId"] = 1; // Type = Weather station + return feature; + }); + geoJson["features"] = filteredFeatures; + const isPoiMap = true; // Map should enable selection of pois + const allowNewPoints = false; // User should not be able to create new pois + const poiMapInstance = new MapModal('poi-map', geoJson, 'nb', isPoiMap, allowNewPoints, selectPoi); + const selectedPoiId = parseInt(selectWeatherstationElement.options[selectWeatherstationElement.selectedIndex].value); + poiMapInstance.openModal(selectedPoiId >= 0 ? selectedPoiId : null); + }) + .catch(error => { + console.error('Unable to retrieve weatherstation geojson', error); + }); + } + + window.storeUserSettings = function() { + return; + let userSettings = {}; + userSettingsFields.forEach((fieldId) => { + userSettings[`{{ form_id }}.${fieldId}`] = document.getElementById(fieldId).value; + }); + userSettingsRadios.forEach((radioName) => { + document.getElementsByName(radioName).forEach((radioElement) => { + if(radioElement.checked) + { + userSettings[`{{ form_id }}.${radioName}`] = radioElement.value; + } + }); + + }); + storeLocalSettings(userSettings); + } </script> {% endblock %} diff --git a/fusarium/views.py b/fusarium/views.py index 2b828d279fe474501b2cbe6838bd98eeda8170f0..b439110301024f180975a8fc2b78cf1be290d569 100755 --- a/fusarium/views.py +++ b/fusarium/views.py @@ -19,10 +19,13 @@ # # from django.shortcuts import render +from django.conf import settings # Create your views here. - def index(request): - context = {} + context = { + "VIPSLOGIC_URL" : f"{settings.VIPSLOGIC_PROTOCOL}://{settings.VIPSLOGIC_SERVER_NAME}", + "form_id": "oatFloweringForm" + } return render(request, 'fusarium/oat_flowering.html', context) \ No newline at end of file