diff --git a/VIPSWeb/local_settings_sample.py b/VIPSWeb/local_settings_sample.py index 466a2c166494e8bb09326682fc199d5ef5e3a4f2..58088731eddb2e4f917e142243ad5af2547b9db4 100755 --- a/VIPSWeb/local_settings_sample.py +++ b/VIPSWeb/local_settings_sample.py @@ -165,6 +165,10 @@ MAP_WARNING_LEGEND_PLACEMENT = 4 # The attribution text that appears in a corner of the map MAP_ATTRIBUTION = "© <a href='http://www.openstreetmap.org'>OpenStreetMap</a> contributors" +# Geoapify is used for getting information (timezone etc) about a given point (latitude, longitude) +# Register at https://www.geoapify.com, and create an API key in order to be able to use the API +GEOAPIFY_API_KEY='api-key' + # The message tags to use on the front page FRONTPAGE_MESSAGE_TAG_IDS = [1,2,3] diff --git a/VIPSWeb/settings.py b/VIPSWeb/settings.py index ee36cbb49cc99254eea6d7fdf9d709ead9fd2e80..d91aa417e6de0beefa02df84048172d5db67784e 100755 --- a/VIPSWeb/settings.py +++ b/VIPSWeb/settings.py @@ -82,9 +82,6 @@ STATICFILES_FINDERS = ( # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) - - - MIDDLEWARE = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/VIPSWeb/static/js/util.js b/VIPSWeb/static/js/util.js index 633a3ccc8fe992d4d4ecaba00e48cc040852838f..b0bc1487b8ba89338ac8bcf0603b27466a873de0 100755 --- a/VIPSWeb/static/js/util.js +++ b/VIPSWeb/static/js/util.js @@ -204,6 +204,18 @@ function getLocalSettings(keys, allOrNothing) return (allOrNothing && keys.length != Object.keys(retVal).length) ? null : retVal; } +/** + * Namespace all strings in given array + */ +function getNameSpaced(nameSpace, anArray) { + var retVal = []; + for(var i = 0; i<anArray.length;i++) + { + retVal.push(nameSpace + "." + anArray[i]); + } + return retVal; +}; + /** * Converts a string like this "6,3,23,1,100" => [6,3,23,1,100] * @param csvString @@ -421,3 +433,28 @@ function getLocalizedCropCategoryName(cropCategory) // Then we give up return gettext("Unnamed"); } + +async function getLocationInformation(latitude, longitude) { + let location = "Unknown" + let timezone = "Europe/Oslo" + + try { + const response = await fetch(`https://api.geoapify.com/v1/geocode/reverse?lat=${latitude}&lon=${longitude}&format=json&apiKey=${settings.geoapifyApiKey}`); + const respJson = await response.json() + if(respJson.results && respJson.results.length > 0) { + location = respJson.results[0].city ? respJson.results[0].city : respJson.results[0].formatted; + timezone = respJson.results[0].timezone.name; + } else { + console.error("Unable to get location information for for lat=" + latitude + " lon=" + longitude) + } + } catch (error) { + console.error("Error fetching location information", error) + } + return { + latitude: latitude, + longitude: longitude, + location: location, + timezone: timezone + } +} + diff --git a/VIPSWeb/templates/settings.js b/VIPSWeb/templates/settings.js index 4659ca6ce179995c8f5ceb19660448d02956439b..afe28c9b8d9552e17d2edaf383904255f0d337e9 100755 --- a/VIPSWeb/templates/settings.js +++ b/VIPSWeb/templates/settings.js @@ -43,6 +43,8 @@ var settings = { userIsIE: {{user_is_ie|yesno:"true,false"}}, + geoapifyApiKey: "{{settings.GEOAPIFY_API_KEY}}", + highchartsGlobalOptions: { lang: { shortMonths: [gettext("Jan"),gettext("Feb"),gettext("Mar"),gettext("Apr"),gettext("May"),gettext("Jun"),gettext("Jul"),gettext("Aug"),gettext("Sep"),gettext("Oct"),gettext("Nov"),gettext("Dec")], diff --git a/cerealblotchmodels/static/cerealblotchmodels/formdefinitions/septoriaHumidityForm.json b/cerealblotchmodels/static/cerealblotchmodels/formdefinitions/septoriaHumidityForm.json index 6005dbd07334b626f39a822c20284f1178f8c84d..915954d62d382f9efdaa1bc8fe73ab21c2c74e96 100755 --- a/cerealblotchmodels/static/cerealblotchmodels/formdefinitions/septoriaHumidityForm.json +++ b/cerealblotchmodels/static/cerealblotchmodels/formdefinitions/septoriaHumidityForm.json @@ -18,18 +18,28 @@ ], "_comment" : "Structure of the septoriaHumidityForm and how to validate it", "fields": [ + { + "name" : "latitude", + "dataType" : "DOUBLE", + "required" : false + }, + { + "name" : "longitude", + "dataType" : "DOUBLE", + "required" : false + }, { "name" : "organizationId_countryCode", "dataType" : "STRING", "fieldType" : "SELECT_SINGLE", - "required" : true, + "required" : false, "nullValue" : "None" }, { "name" : "weatherStationId", "dataType" : "STRING", "fieldType" : "SELECT_SINGLE", - "required" : true, + "required" : false, "nullValue" : "" }, { diff --git a/cerealblotchmodels/templates/cerealblotchmodels/barleynetblotchform.html b/cerealblotchmodels/templates/cerealblotchmodels/barleynetblotchform.html index 7e22cc8d3043956af359b747066608c835c79465..eb50a7b890a3e3d936c8ef19a16011303c8bde4d 100755 --- a/cerealblotchmodels/templates/cerealblotchmodels/barleynetblotchform.html +++ b/cerealblotchmodels/templates/cerealblotchmodels/barleynetblotchform.html @@ -32,6 +32,7 @@ <link type="text/css" rel="stylesheet" href="https://logic.testvips.nibio.no/css/mapModal.css" /> <style> + /* Added when integrating map, should perhaps be moved to main css file. */ input#latitude, input#longitude { width: 30%; display: inline-block; @@ -42,12 +43,15 @@ display: inline-block; margin: 10px 10px 10px 0; } - .main-label { - font-size: 1.8rem; - font-weight: 500 !important; + fieldset { + margin-bottom: 30px; } - .space { - margin-top: 40px; + .btn-map { + margin-top: 5px; + margin-bottom: 20px; + } + #gridPointInfo { + margin: 10px 0; } </style> {% endblock %} @@ -63,99 +67,112 @@ <div class="row"> <div class="col-md-12 mb-3"> <p class="lead"> - <a href="https://www.vips-landbruk.no/forecasts/models/BARLEYNETB/">Elens modell for byggbrunflekk</a> lager en tilvekstkurve for byggbrunflekk med værdata som grunnlag. - Du kan velge å kjøre modellen med værdata for et vilkårlig punkt i kartet, eller fra en værstasjon. Det er i tillegg nødvendig å legge inn informasjon om angrep av sjukdommen, samt - sådato, sort, vekstskifte og jordarbeiding. Dersom du har en privat værstasjon som er registrert i Vips, vil denne være tilgjengelig i skjemaet etter <a href="https://logic.vips.nibio.no/login">innlogging</a>. + <a href="https://www.vips-landbruk.no/forecasts/models/BARLEYNETB/">Elens modell for byggbrunflekk</a> lager en utviklingskurve for byggbrunflekk med værdata som grunnlag. + Du kan velge å kjøre modellen med værdata for et vilkårlig punkt i kartet, eller fra en værstasjon. Det er i tillegg nødvendig å legge inn informasjon om nivået på angrep av sjukdommen, samt + sådato, sort, vekstskifte og jordarbeiding. Dersom du har en <a href="https://www.vips-landbruk.no/information/27/">privat værstasjon</a> som er registrert i VIPS, vil denne være tilgjengelig i skjemaet etter <a href="https://logic.vips.nibio.no/login">innlogging</a>. </p> </div> </div> <div class="row"> <div class="col-md-6"> <input type="hidden" name="timeZone" value="Europe/Oslo"/> - <div class="form-group"> - <label class="main-label">Jeg vil bruke værdata</label> - <div class="radio"> - <label> - <input type="radio" name="weatherdataType" id="coordinates" value="coordinates" checked onchange="displayCoordinatesInput()"> - for et spesifikt punkt (koordinater) - </label> - <div id="input-coordinates"> - <input type="text" class="form-control" name="latitude" id="latitude" placeholder="Breddegrad" aria-label="Breddegrad"> - <input type="text" class="form-control" name="longitude" id="longitude" placeholder="Lengdegrad" aria-label="Lengdegrad"> - <button type="button" class="btn btn-primary" onclick="openCoordinatesMap()"><i class="fa fa-map-marker fa-lg"></i> Velg i kart</button> + <fieldset> + <legend>Jeg vil bruke værdata</legend> + <div class="form-group"> + <div class="radio"> + <label> + <input type="radio" name="weatherDataSourceType" id="grid" value="grid" onchange="displayCoordinatesInput()"> + for et punkt i kartet + </label> + <div id="input-coordinates"> + <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 btn-map" onclick="openCoordinatesMap()"><i class="fa fa-map-marker fa-lg"></i> Velg i kart</button> + </div> + <div id="coordinates-map" class="map-modal"></div> </div> - <div id="coordinates-map" class="map-modal"></div> - </div> - <div class="radio"> - <label> - <input type="radio" name="weatherdataType" id="weatherstation" value="weatherstation" onchange="displayWeatherstationInput()"> - fra en værstasjon - </label> - <div id="input-weatherstation" style="display: none;"> - <select name="weatherStationId" id="weatherStationId" class="form-control" onblur="validateField(this);"></select> - <button type="button" class="btn btn-primary" onclick="openPoiMap()"><i class="fa fa-map-marker fa-lg"></i> Velg i kart</button> + <div class="radio"> + <label> + <input type="radio" name="weatherDataSourceType" id="weatherstation" value="weatherstation" onchange="displayWeatherstationInput()"> + fra en værstasjon + </label> + <div id="input-weatherstation" style="display: none;"> + <select name="weatherStationId" id="weatherStationId" class="form-control"> + <option value="">Velg værstasjon</option> + </select> + <button type="button" class="btn btn-primary btn-map" onclick="openPoiMap()"><i class="fa fa-map-marker fa-lg"></i> Velg i kart</button> + </div> + <div id="poi-map" class="map-modal"></div> </div> - <div id="poi-map" class="map-modal"></div> + <span class="help-block" id="{{ form_id }}_latitude_validation"></span> + <span class="help-block" id="{{ form_id }}_longitude_validation"></span> + <span class="help-block" id="{{ form_id }}_weatherStationId_validation"></span> + </div> + </fieldset> + <fieldset> + <legend>Jeg dyrker</legend> + <div class="form-group"> + <select name="cropOrganismId" id="cropOrganismId" class="form-control" onblur="validateField(this);"> + </select> + <span class="help-block" id="{{ form_id }}_cropOrganismId_validation"></span> + </div> + <div class="form-group"> + <label for="sowingDate">{% trans "Sowing date" %}</label> + <input type="date" id="sowingDate" name="sowingDate" class="form-control" max="{{max_sowing_date|date:'Y-m-d'}}" onblur="validateField(this);" placeholder="Sådato"/> + <span class="help-block" id="{{ form_id }}_sowingDate_validation"></span> + </div> + <div class="form-group"> + <label for="sameCropAsLastSeason">Vekstskifte</label><br> + <input type="checkbox" id="sameCropAsLastSeason" name="sameCropAsLastSeason"/> Jeg dyrket bygg på samme skifte i fjor<br/> + <span class="help-block" id="{{ form_id }}_sameCropAsLastSeason_validation"></span> </div> - <span class="help-block" id="{{ form_id }}_latitude_validation"></span> - <span class="help-block" id="{{ form_id }}_weatherStationId_validation"></span> - <span class="help-block" id="{{ form_id }}_longitude_validation"></span> - </div> - <div class="form-group space"> - <label class="main-label" for="cropOrganismId">Jeg har dyrket</label> - <select name="cropOrganismId" id="cropOrganismId" class="form-control" onblur="validateField(this);"> - </select> - <span class="help-block" id="{{ form_id }}_cropOrganismId_validation"></span> - </div> - <div class="form-group"> - <label for="sowingDate">{% trans "Sowing date" %}</label> - <input type="date" id="sowingDate" name="sowingDate" class="form-control" max="{{max_sowing_date|date:'Y-m-d'}}" onblur="validateField(this);" placeholder="Sådato"/> - <span class="help-block" id="{{ form_id }}_sowingDate_validation"></span> - </div> - <div class="form-group"> - <label for="sameCropAsLastSeason">Vekstskifte</label><br> - <input type="checkbox" id="sameCropAsLastSeason" name="sameCropAsLastSeason"/> Jeg dyrket bygg på samme skifte i fjor<br/> - <span class="help-block" id="{{ form_id }}_sameCropAsLastSeason_validation"></span> - </div> - <div class="form-group"> - <label for="plowed">Jordarbeiding</label><br> - <input type="checkbox" id="plowed" name="plowed"/> Jeg har pløyd<br/> - <span class="help-block" id="{{ form_id }}_plowed_validation"></span> - </div> + <div class="form-group"> + <label for="plowed">Jordarbeiding</label><br> + <input type="checkbox" id="plowed" name="plowed"/> Jeg har pløyd<br/> + <span class="help-block" id="{{ form_id }}_plowed_validation"></span> + </div> + </fieldset> </div> <div class="col-md-6"> - <div class="form-group"> - <label for="observationDate" class="main-label" for="observationDate">Observasjonsdato</label> - <input type="date" id="observationDate" name="observationDate" class="form-control"/> - <span class="help-block" id="{{ form_id }}_observationDate_validation"></span> - </div> - <div class="form-group"> - <label for="observationValue">{% trans "% Infected leaves" %}</label> - <input type="number" id="observationValue" name="observationValue" class="form-control" min="0" max="100"/> - <span class="help-block" id="{{ form_id }}_observationValue_validation"></span> - </div> - <div class="form-group space"> - <label for="sprayingDate" class="main-label">Sprøytedato</label> - <input type="date" id="sprayingDate" name="sprayingDate" class="form-control"/> - <span class="help-block" id="{{ form_id }}_sprayingDate_validation"></span> - </div> - <div class="form-group"> - <label for="preparationId">med preparat</label> - <select name="preparationId" id="preparationId" class="form-control"> - </select> - <span class="help-block" id="{{ form_id }}_preparationId_validation"></span> - </div> - <div class="form-group"> - <label for="preparationDose">og preparatdose (ml/daa)</label> - <input type="number" id="preparationDose" name="preparationDose" class="form-control"/> - <span class="help-block" id="{{ form_id }}_preparationDose_validation"></span> - </div> - + <fieldset> + <legend>Observasjon</legend> + <div class="form-group"> + <p><i>Legg inn dato og omfang dersom du har observert symptomer</i></p> + <input type="date" id="observationDate" name="observationDate" class="form-control"/> + <span class="help-block" id="{{ form_id }}_observationDate_validation"></span> + </div> + <div class="form-group"> + <label for="observationValue">% blad med symptomer</label> + <input type="number" id="observationValue" name="observationValue" class="form-control" min="0" max="100"/> + <span class="help-block" id="{{ form_id }}_observationValue_validation"></span> + </div> + </fieldset> + <fieldset> + <legend>Sprøyting</legend> + <div class="form-group"> + <p><i>Legg inn dato og detaljer for eventuell siste sprøyting</i></p> + <input type="date" id="sprayingDate" name="sprayingDate" class="form-control"/> + <span class="help-block" id="{{ form_id }}_sprayingDate_validation"></span> + </div> + <div class="form-group"> + <label for="preparationId">med preparat</label> + <select name="preparationId" id="preparationId" class="form-control"> + </select> + <span class="help-block" id="{{ form_id }}_preparationId_validation"></span> + </div> + <div class="form-group"> + <label for="preparationDose">og preparatdose (ml/daa)</label> + <input type="number" id="preparationDose" name="preparationDose" class="form-control"/> + <span class="help-block" id="{{ form_id }}_preparationDose_validation"></span> + </div> + </fieldset> </div> </div> <div class="row"> <div class="col-md-12 form-group"> - <button type="button" class="btn btn-primary pull-right" onclick="if(validateForm(document.getElementById('{{ form_id }}')) & validateFormExtra()){runModel();}">{% trans "Run model" %}</button> + <button type="button" class="btn btn-primary pull-right" onclick="if(validateForm(document.getElementById('{{ form_id }}')) & validateFormExtra()){storeUserSettings();runModel();}">{% trans "Run model" %}</button> </div> </div> @@ -195,14 +212,34 @@ import MapModal from 'https://logic.testvips.nibio.no/js/mapModal.js'; //import MapModal from settings.vipslogicProtocol + "://" + settings.vipslogicServerName + "/js/mapModal.js" + const theForm = document.getElementById("{{ form_id }}"); const inputLatitudeElement = document.getElementById("latitude"); const inputLongitudeElement = document.getElementById("longitude"); + const radioGrid = document.getElementById("grid") + const radioWeatherstation = document.getElementById("weatherstation") const selectWeatherstationElement = document.getElementById("weatherStationId"); let poiIdList = [] let selectedPoint = null; let selectedFeature = undefined; + const formFields = [ + "weatherDataSourceType", + "latitude", + "longitude", + "timezone", + "weatherStationId", + "cropOrganismId", + "sowingDate", + "sameCropAsLastSeason", + "plowed", + "observationDate", + "observationValue", + "sprayingDate", + "preparationId", + "preparationDose" + ]; + function getSelectedPoiId() { const value = selectWeatherstationElement.value; const parsedValue = parseInt(value, 10); @@ -216,6 +253,7 @@ if(selectedLatitude && selectedLongitude) { inputLatitudeElement.value = selectedLatitude; inputLongitudeElement.value = selectedLongitude; + getTimezoneForPoint(selectedLatitude, selectedLongitude); } } @@ -231,7 +269,7 @@ window.openCoordinatesMap = () => { if (inputLatitudeElement.value && inputLongitudeElement.value) { - selectedPoint = 1; + selectedPoint = -1; selectedFeature = { "type": "FeatureCollection", "features": [ { @@ -250,11 +288,9 @@ selectedFeature = undefined; } - // TODO Open map with currently selected language! (not 'nb') - 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); + const allowNewPoints = true; // User should be able to select new coordinates + const coordinatesMapInstance = new MapModal('coordinates-map', selectedFeature, settings.currentLanguage, isPoiMap, allowNewPoints, selectCoordinates); coordinatesMapInstance.openModal(selectedPoint); } @@ -271,7 +307,7 @@ .then(geoJson => { 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 poiMapInstance = new MapModal('poi-map', geoJson, settings.currentLanguage, isPoiMap, allowNewPoints, selectPoi); const selectedPoiId = getSelectedPoiId(); poiMapInstance.openModal(selectedPoiId); }) @@ -281,36 +317,56 @@ } window.displayWeatherstationInput = () => { - document.getElementById("weatherstation").checked = true; + radioWeatherstation.checked = true; document.getElementById('input-weatherstation').style.display="block"; document.getElementById('input-coordinates').style.display="none"; - inputLatitudeElement.value = "" - inputLongitudeElement.value = "" } - window.displayCoordinatesInput = (id) => { - document.getElementById("coordinates").checked = true; + window.displayCoordinatesInput = () => { + radioGrid.checked = true; document.getElementById('input-weatherstation').style.display="none"; document.getElementById('input-coordinates').style.display="block"; - selectWeatherstationElement.selectedIndex = 0; + getTimezoneForSelectedPoint() + } + + const getTimezoneForPoint = (latitude, longitude) => { + getLocationInformation(latitude, longitude).then(locationInfo => { + theForm["timezone"].value = locationInfo.timezone; + document.getElementById("gridPointInfo").innerHTML = `<b>Sted</b> ${locationInfo.location}<br> + <b>Breddegrad</b> ${locationInfo.latitude}<br> + <b>Lengdegrad</b> ${locationInfo.longitude}<br> + <b>Tidssone</b> ${locationInfo.timezone}` + }); + } + + const getTimezoneForSelectedPoint = () => { + const lat = theForm["latitude"].value; + const lon = theForm["longitude"].value; + if(lat && lon) { + getTimezoneForPoint(lat, lon) + } else { + console.info("Latitude and longitude not set in form, cannot get timezone information") + } } window.validateFormExtra = () => { - var theForm = document.getElementById("{{ form_id }}"); // Location: Either weatherStationId or latitude/longitude must be set - const selectedWeatherdataType = theForm.querySelector('input[name="weatherdataType"]:checked').value; - if( selectedWeatherdataType === "coordinates") { + const selectedWeatherdataType = theForm.querySelector('input[name="weatherDataSourceType"]:checked').value; + if( selectedWeatherdataType === "grid") { const trimmedLat = theForm["latitude"].value.trim() const trimmedLon = theForm["longitude"].value.trim() if(trimmedLat === "" || trimmedLon === "" || isNaN(Number(trimmedLat)) || isNaN(Number(trimmedLon))) { alert("Mangler gyldig punkt"); return false; } - } - if (selectedWeatherdataType === "weatherstation") { + } else if (selectedWeatherdataType === "weatherstation") { if(theForm["weatherStationId"].options[theForm["weatherStationId"].selectedIndex].value == "-1") { alert("Mangler værstasjon") + return false; } + } else { + alert("Mangler type værdatakilde") + return false; } // Observation: Either no fields or all fields must be set @@ -331,7 +387,35 @@ return true; } - var VIPSOrganizationId = {{vips_organization_id}}; + window.storeUserSettings = () => { + var settingsDict = {} + for(var i in formFields) { + const inputElement = theForm[formFields[i]]; + const key = "{{ form_id }}" + "." + formFields[i]; + // Need to add the formId as namespace to avoid confusion in Local Storage + if(inputElement.type === "checkbox") { + settingsDict[key] = inputElement.checked + } else { + settingsDict[key] = inputElement.value; + } + } + storeLocalSettings(settingsDict); + }; + + window.renderUserSettings = (userSettings) => { + for(const key in userSettings){ + const fieldName = key.substring("{{form_id}}.".length); + const inputElement = theForm[fieldName]; + const theValue = userSettings[key]; + if (inputElement.type && inputElement.type === "checkbox") { + inputElement.checked = theValue === "true" || theValue === true; + } else { + inputElement.value = theValue; + } + } + }; + + const VIPSOrganizationId = {{vips_organization_id}}; window.runModel = () => { document.getElementById("results").style.display="none"; document.getElementById("errorMessageContainer").style.display="none"; @@ -434,9 +518,8 @@ }; - function initWeatherStations(){ - // Fetching information asynchronously from server - var request = $.ajax({ + async function initWeatherStations(){ + return $.ajax({ type:"GET", url: settings.vipslogicProtocol + "://" + settings.vipslogicServerName + "/rest/poi/organization/" + VIPSOrganizationId, statusCode:{ @@ -462,14 +545,13 @@ function initCrops() { - // Fetching information asynchronously from server - var request = $.ajax({ + return $.ajax({ type:"GET", url: settings.vipslogicProtocol + "://" + settings.vipslogicServerName + "/rest/barleynetblotchmodel/barleyvarieties/" + VIPSOrganizationId, statusCode:{ 200: function(data,textStatus, jqXHR){ // Building result HTML - var cropHTML=["<option value=\"-1\">-- {% trans "Select crop" %} --</option>"]; + var cropHTML=["<option value=\"-1\">-- Velg sort --</option>"]; data.sort(compareOrganisms); for(var i in data) { @@ -490,8 +572,7 @@ function initPreparations() { - // Fetching information asynchronously from server - var request = $.ajax({ + return $.ajax({ type:"GET", url: settings.vipslogicProtocol + "://" + settings.vipslogicServerName + "/rest/barleynetblotchmodel/preparations/" + VIPSOrganizationId, statusCode:{ @@ -522,18 +603,33 @@ document.getElementById("errorMessage").innerHTML = "<h1>Error</h1><pre>" + message + "</pre>"; document.getElementById("errorMessageContainer").style.display="block"; }; - + $(document).ready(function() { if(settings.userIsIE) { alert("{% trans "WARNING: We suspect you are using Internet Explorer to view this site. VIPS is not designed to work with Internet Explorer, you may experience errors and missing features. Please use a different browser, like Microsoft Edge or Google Chrome." %}"); } - initWeatherStations(); - initCrops(); - initPreparations(); - // Init form validation - loadFormDefinition("{{ form_id }}","/static/cerealblotchmodels/formdefinitions/"); - displayCoordinatesInput(); + + // Make sure all promises have returned before continuing + Promise.all([initWeatherStations(), initCrops(), initPreparations()]).then(function() { + loadFormDefinition("{{ form_id }}","/static/cerealblotchmodels/formdefinitions/"); + + var userSettings = getLocalSettings(getNameSpaced("{{ form_id }}",formFields), false); + if(!isDictEmpty(userSettings)) { + renderUserSettings(userSettings); + if(theForm["weatherDataSourceType"].value == "grid") { + displayCoordinatesInput(); + } else if(theForm["weatherDataSourceType"].value == "weatherstation") { + displayWeatherstationInput(); + } else { + displayCoordinatesInput(); + } + } else { + displayCoordinatesInput(); + } + }).catch(function(error) { + console.error("Error initializing data:", error); + }); }); </script> diff --git a/cerealblotchmodels/templates/cerealblotchmodels/septoriahumiditymodelform.html b/cerealblotchmodels/templates/cerealblotchmodels/septoriahumiditymodelform.html index 65632da22e6ec598c51675b410e280c45bef4cba..0645aacb254323cfefd3b29ae22f502492e55267 100644 --- a/cerealblotchmodels/templates/cerealblotchmodels/septoriahumiditymodelform.html +++ b/cerealblotchmodels/templates/cerealblotchmodels/septoriahumiditymodelform.html @@ -23,32 +23,82 @@ {% endcomment %} {% load i18n %} {% block title %}{% trans "Septoria humidity model" %}{% endblock %} + +{% block customCSS %} +<link type="text/css" rel="stylesheet" href="https://logic.testvips.nibio.no/css/3rdparty/leaflet.css" /> +<link type="text/css" rel="stylesheet" href="https://logic.testvips.nibio.no/css/mapModal.css" /> + +<style> + /* Added when integrating map, should perhaps be moved to main css file. */ + input#latitude, input#longitude { + margin: 10px 10px 10px 0; + } + select#organizationId_countryCode, select#weatherStationId { + margin: 5px; + } + .main-label { + font-size: 1.8rem; + font-weight: 500 !important; + } + .space { + margin-top: 40px; + } + .radio { + margin-top: 0; + margin-bottom: 20px; + } + #gridPointInfo { + margin: 10px 0; + } +</style> +{% endblock %} + {% block content %} <div class="singleBlockContainer"> <h1>{% trans "Septoria humidity model" %}</h1> - <p>{% trans "Fuktmodellen er et beslutningsstøtteverktøy, utviklet av <a href='https://www.seges.dk/'>SEGES</a>, Danmark, for å kunne vurdere risiko for angrep av hvetebladprikk i høsthvete under danske forhold. <a href='/forecasts/models/SEPTORIAHU/' target='new'>Les mer</a>, og se <a href='https://vimeo.com/818734601' target='new'>informasjonsvideo</a>" %}</p> + <p class="lead">{% trans "Fuktmodellen er et beslutningsstøtteverktøy, utviklet av <a href='https://www.seges.dk/'>SEGES</a>, Danmark, for å kunne vurdere risiko for angrep av hvetebladprikk i høsthvete under danske forhold. <a href='/forecasts/models/SEPTORIAHU/' target='new'>Les mer</a>, og se <a href='https://vimeo.com/818734601' target='new'>informasjonsvideo</a>" %}</p> <form role="form" id="{{ form_id }}"> - <div class="row"> - <div class="col-md-12"> - <h2>{% trans "Background data" %}</h2> - </div> - </div> <div class="row"> <div class="col-md-3"> - <div class="form-group"> - <label for="organizationId_countryCode">{% trans "Country" %}</label> - <select name="organizationId_countryCode" id="organizationId_countryCode" class="form-control" onchange="updateWeatherDataSources(this.options[this.options.selectedIndex].value);"> - <option value="None">{% trans "Please select" %}</option> - </select> - <span class="help-block" id="{{ form_id }}_organizationId_countryCode_validation"></span> - </div> - <div class="form-group"> - <label for="weatherStationId">{% trans "Weather station" %}</label> - <select name="weatherStationId" id="weatherStationId" class="form-control"> - <option value="">{% trans "Please select" %}</option> - </select> - <span class="help-block" id="{{ form_id }}_weatherStationId_validation"></span> - </div> + <fieldset> + <legend>Værdata</legend> + <div class="form-group"> + <div class="radio"> + <label> + <input type="radio" name="weatherDataSourceType" id="grid" value="grid" onchange="displayCoordinatesInput()"> + for et punkt i kartet + </label> + <div id="input-coordinates"> + <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()"><i class="fa fa-map-marker fa-lg"></i> Velg i kart</button> + </div> + <div id="coordinates-map" class="map-modal"></div> + </div> + <div class="radio"> + <label> + <input type="radio" name="weatherDataSourceType" id="weatherstation" value="weatherstation" onchange="displayWeatherstationInput()"> + fra en værstasjon + </label> + <div id="input-weatherstation" style="display: none;"> + <select name="organizationId_countryCode" id="organizationId_countryCode" class="form-control" onchange="updateWeatherDataSources(this.options[this.options.selectedIndex].value);"> + <option value="None">Velg land</option> + </select> + <select name="weatherStationId" id="weatherStationId" class="form-control" disabled> + <option value="">Velg værstasjon</option> + </select> + <button disabled type="button" id="poi-map-button" class="btn btn-primary" onclick="openPoiMap()"><i class="fa fa-map-marker fa-lg"></i> Velg i kart</button> + </div> + <div id="poi-map" class="map-modal"></div> + </div> + <span class="help-block" id="{{ form_id }}_organizationId_countryCode_validation"></span> + <span class="help-block" id="{{ form_id }}_weatherStationId_validation"></span> + <span class="help-block" id="{{ form_id }}_latitude_validation"></span> + <span class="help-block" id="{{ form_id }}_longitude_validation"></span> + </div> + </fieldset> <fieldset> <legend>{% trans "Sprayings" %}</legend> <div class="form-group"> @@ -62,8 +112,6 @@ <span class="help-block" id="{{ form_id }}_dateSpraying2_validation"></span> </div> </fieldset> - - </div> <div class="col-md-3"> <fieldset> @@ -94,7 +142,9 @@ <span class="help-block" id="{{ form_id }}_dateGs75_validation"></span> </div> </fieldset> - {% trans "Show advanced settings" %} <input type="checkbox" onclick="toggleAdvancedColumns(this);" autocomplete="off"/> + <div class="pull-right"> + {% trans "Show advanced settings" %} <input type="checkbox" onclick="toggleAdvancedColumns(this);" autocomplete="off"/> + </div> </div> <div class="col-md-3"> @@ -149,8 +199,8 @@ </div> </div> <div class="row"> - <div class="col-md-12 form-group"> - <button type="button" class="btn btn-primary" onclick="if(validateForm(document.getElementById('{{ form_id }}'))){storeUserSettings();runModel();}">{% trans "Run model" %}</button> + <div class="col-md-6 form-group"> + <button type="button" class="btn btn-primary pull-right" onclick="if(validateForm(document.getElementById('{{ form_id }}')) & validateFormExtra()){storeUserSettings();runModel();}">{% trans "Run model" %}</button> </div> </div> </form> @@ -179,11 +229,139 @@ <script type="text/javascript" src="{% static "js/util.js" %}"></script> <script type="text/javascript" src="{% static "js/validateForm.js" %}"></script> <script type="text/javascript" src="{% static "forecasts/js/forecasts.js" %}"></script> -<script type="text/javascript"> +<script type="module"> + import MapModal from 'https://logic.testvips.nibio.no/js/mapModal.js'; + //import MapModal from settings.vipslogicProtocol + "://" + settings.vipslogicServerName + "/js/mapModal.js" + + const theForm = document.getElementById("{{ form_id }}"); + const inputLatitudeElement = document.getElementById("latitude"); + const inputLongitudeElement = document.getElementById("longitude"); + const selectWeatherstationElement = document.getElementById("weatherStationId"); + const openPoiMapButton = document.getElementById("poi-map-button"); + + let poiIdList = [] + + let selectedPoint = null; + let selectedFeature = undefined; + + function getSelectedPoiId() { + const value = selectWeatherstationElement.value; + const parsedValue = parseInt(value, 10); + return (!isNaN(parsedValue) && parsedValue > 0) ? parsedValue : undefined; + } + + 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); + } + } + + 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.openCoordinatesMap = () => { + if (inputLatitudeElement.value && inputLongitudeElement.value) { + console.info(`Open map for lat=${parseFloat(inputLatitudeElement.value)} lon=${parseFloat(inputLatitudeElement.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 coordinates + const coordinatesMapInstance = new MapModal('coordinates-map', selectedFeature, settings.currentLanguage, isPoiMap, allowNewPoints, selectCoordinates); + coordinatesMapInstance.openModal(selectedPoint); + } + + window.openPoiMap = () => { + fetch(settings.vipslogicProtocol + "://" + settings.vipslogicServerName + "/rest/poi/geojson", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(poiIdList) + }) + .then(response => response.json()) + .then(geoJson => { + 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, settings.currentLanguage, isPoiMap, allowNewPoints, selectPoi); + const selectedPoiId = getSelectedPoiId(); + poiMapInstance.openModal(selectedPoiId); + }) + .catch(error => { + console.error('Unable to retrieve weatherstation geojson', error); + }); + } + + window.displayWeatherstationInput = () => { + document.getElementById("weatherstation").checked = true; + document.getElementById('input-weatherstation').style.display="block"; + document.getElementById('input-coordinates').style.display="none"; + } + + window.displayCoordinatesInput = (id) => { + document.getElementById("grid").checked = true; + document.getElementById('input-weatherstation').style.display="none"; + document.getElementById('input-coordinates').style.display="block"; + getTimezoneForSelectedPoint() + } + + const getTimezoneForPoint = (latitude, longitude) => { + getLocationInformation(latitude, longitude).then(locationInfo => { + theForm["timezone"].value = locationInfo.timezone; + document.getElementById("gridPointInfo").innerHTML = `<b>Sted</b> ${locationInfo.location}<br> + <b>Breddegrad</b> ${locationInfo.latitude}<br> + <b>Lengdegrad</b> ${locationInfo.longitude}<br> + <b>Tidssone</b> ${locationInfo.timezone}` + }); + } + + const getTimezoneForSelectedPoint = () => { + const lat = theForm["latitude"].value; + const lon = theForm["longitude"].value; + if(lat && lon) { + getTimezoneForPoint(lat, lon) + } else { + console.info("Latitude and longitude not set in form, cannot get timezone information") + } + } + var danishPostCodesUTM; var organizations; var allowedCountryCodes = ["NO","DK","SE","FI","LT"]; var formFields = [ + "weatherDataSourceType", + "latitude", + "longitude", + "timezone", "organizationId_countryCode", "weatherStationId", "dateSpraying1", @@ -217,18 +395,46 @@ var userSettings = getLocalSettings(getNameSpaced("{{ form_id }}",formFields), false); if(!isDictEmpty(userSettings)) { renderUserSettings(userSettings); + if(theForm["weatherDataSourceType"].value == "grid") { + displayCoordinatesInput(); + } else if(theForm["weatherDataSourceType"].value == "weatherstation") { + displayWeatherstationInput(); + } else { + displayCoordinatesInput(); + } } else { + displayCoordinatesInput(); updateGSDates(); } }); - }); - - }); - + + window.validateFormExtra = () => { + // Location: Either weatherStationId or latitude/longitude must be set + const selectedWeatherdataType = theForm.querySelector('input[name="weatherDataSourceType"]:checked').value; + if( selectedWeatherdataType === "grid") { + const trimmedLat = theForm["latitude"].value.trim() + const trimmedLon = theForm["longitude"].value.trim() + if(trimmedLat === "" || trimmedLon === "" || isNaN(Number(trimmedLat)) || isNaN(Number(trimmedLon))) { + alert("Mangler gyldig punkt"); + return false; + } + } + else if (selectedWeatherdataType === "weatherstation") { + if(theForm["weatherStationId"].options[theForm["weatherStationId"].selectedIndex].value == "-1") { + alert("Mangler værstasjon"); + return false; + } + } else { + alert("Mangler type værdatakilde") + return false; + } + return true; + } + var initDanishPostCodesUTM = function(callback){ $.ajax({ type:"GET", @@ -279,13 +485,13 @@ } }); }; - - var updateWeatherDataSources = function(organizationId_countryCode){ - var selectList = document.getElementById("weatherStationId"); - selectList.options.length = 1; // Erase all former options + + window.updateWeatherDataSources = (organizationId_countryCode) => { + selectWeatherstationElement.options.length = 1; // Erase all former options - if(organizationId_countryCode === "None") - { + if(organizationId_countryCode === "None"){ + selectWeatherstationElement.disabled = true; + openPoiMapButton.disabled = true; return; } @@ -301,12 +507,13 @@ var city = postCodeEl.getElementsByTagName("CityName")[0].firstChild.nodeValue; var opt = new Option(city,UTM32v); //console.info(opt); - selectList.options[selectList.options.length] = opt; + selectWeatherstationElement.options[selectWeatherstationElement.options.length] = opt; } - renderUserSetting(selectList); + renderUserSetting(selectWeatherstationElement); } else { + poiIdList = [] $.ajax({ type:"GET", url: settings.vipslogicProtocol + "://" + settings.vipslogicServerName + "/rest/poi/organization/" + organizationId, @@ -318,6 +525,7 @@ for(var i in data) { var ws = data[i]; + poiIdList.push(ws["pointOfInterestId"]); wsHTML.push("<option value=\"" + ws["pointOfInterestId"] + "\">" + ws["name"] + "</option>"); } var wsSelect = document.getElementById("weatherStationId"); @@ -331,9 +539,11 @@ } }); } + selectWeatherstationElement.disabled = false; + openPoiMapButton.disabled = false; }; - var updateGSDates = function(){ + window.updateGSDates = () => { var dateGs31 = document.getElementById("dateGs31"); var date3rdUpperLeafEmergingdateGs31 = document.getElementById("date3rdUpperLeafEmerging"); var date2ndUpperLeafEmerging = document.getElementById("date2ndUpperLeafEmerging"); @@ -353,7 +563,7 @@ dateGs75.value = currentDate.format("YYYY-MM-DD"); } - var runModel = function(){ + window.runModel = () => { document.getElementById("chartContainer").style.display="none"; // Hide chart document.getElementById("warningStatusInterpretation").style.display="none"; // Insert please wait message @@ -385,8 +595,7 @@ "SEPTORIAHU.HPHPP" : "{% trans "Humid period hour outside protection period" %}" }; - var renderResults = function(data,textStatus, jqXHR) - { + window.renderResults = (data,textStatus, jqXHR) => { data.sort(compareForecastResults).reverse(); // First attempt: A table! var headingLine = "<tr><td style=\"font-weight: bold;\">{% trans "Time" %}</td>"; @@ -449,11 +658,12 @@ renderForecastChart("chartContainer", "{% trans "Septoria humidity model" %}", warningStatusPlotBandData, data); } - var handleAjaxError = function(jqXHR,textStatus,errorThrown){ + window.handleAjaxError = (jqXHR,textStatus,errorThrown) => { + document.getElementById("resultsTable").innerHTML="" alert(textStatus); }; - var renderUserSettings = function(userSettings){ + window.renderUserSettings = (userSettings) => { // Strip namespace from form field var theForm = document.getElementById('{{ form_id }}'); for(var i in userSettings){ @@ -464,7 +674,7 @@ } }; - var renderUserSetting = function(formField) + window.renderUserSetting = (formField) => { var localStorageKey = "{{form_id}}." + formField.name; @@ -475,16 +685,7 @@ } } - var getNameSpaced = function(nameSpace, anArray){ - var retVal = []; - for(var i = 0; i<anArray.length;i++) - { - retVal.push(nameSpace + "." + anArray[i]); - } - return retVal; - }; - - var storeUserSettings = function(){ + window.storeUserSettings = () => { var theForm = document.getElementById('{{ form_id }}'); var settingsDict = {} for(var i in formFields) @@ -495,7 +696,7 @@ storeLocalSettings(settingsDict); }; - var toggleAdvancedColumns = function(theCheckBox){ + window.toggleAdvancedColumns = (theCheckBox) => { var col1 = document.getElementById("septoriaHumidityAdvancedColumn1"); var col2 = document.getElementById("septoriaHumidityAdvancedColumn2"); col1.style.visibility = theCheckBox.checked ? "visible" : "hidden";