diff --git a/src/main/webapp/js/util.js b/src/main/webapp/js/util.js index 193a56249727b373aa7f9471f457d27a60bc6296..e92ca9fb10d92473489427ecd62fbb7c704c7dd9 100755 --- a/src/main/webapp/js/util.js +++ b/src/main/webapp/js/util.js @@ -17,9 +17,8 @@ * */ -function setFieldValue(theForm, fieldName, value) -{ - theForm[fieldName] = value; +function setFieldValue(theForm, fieldName, value) { + theForm[fieldName] = value; } /* An easy way to sort list options alphabetically @@ -28,43 +27,35 @@ function setFieldValue(theForm, fieldName, value) * @param {type} b * @returns {Number} */ -var compareSelectListOptions = function(a,b) -{ - if(a.text < b.text) - { +var compareSelectListOptions = function (a, b) { + if (a.text < b.text) { return -1; } - if(a.text > b.text) - { + if (a.text > b.text) { return 1; } return 0; }; -var sortListAlphabetically = function(theList, keepFirst) -{ - keepFirst = keepFirst | 0; - var allOptions = []; - for(var i=keepFirst;i<theList.options.length;i++) - { - allOptions.push(theList.options[i]); - } - allOptions.sort(function(a,b){ - if(a.label < b.label) - { - return -1; +var sortListAlphabetically = function (theList, keepFirst) { + keepFirst = keepFirst | 0; + var allOptions = []; + for (var i = keepFirst; i < theList.options.length; i++) { + allOptions.push(theList.options[i]); } - if(a.label > b.label) - { - return 1; + allOptions.sort(function (a, b) { + if (a.label < b.label) { + return -1; + } + if (a.label > b.label) { + return 1; + } + return 0; + }); + theList.options.length = keepFirst; // Keeping the top items? + for (var i = 0; i < allOptions.length; i++) { + theList.options[theList.options.length] = allOptions[i]; } - return 0; - }); - theList.options.length=keepFirst; // Keeping the top items? - for(var i=0;i<allOptions.length;i++) - { - theList.options[theList.options.length] = allOptions[i]; - } }; /** @@ -73,42 +64,35 @@ var sortListAlphabetically = function(theList, keepFirst) * @param language if you want to override system settings, specify language here * @returns {String} */ -function getLocalizedOrganismName(organism, language) -{ - var preferredLanguage = language || environment.currentLanguage; - // Fallback in case nothing works - if(organism === null) - { - return gettext("Unnamed"); - } - // Attempting the following languages (in order): current language, default language, English - var languages = [preferredLanguage, environment.defaultLanguage, "en"]; - for(var j in languages) - { - for(var i in organism.organismLocaleSet) - { - var localeSet = organism.organismLocaleSet[i]; - //console.log(localeSet); - if(localeSet.organismLocalePK.locale == languages[j]) - { - return localeSet.localName; - } - } - } - // Then we try the latin name - if(organism.latinName !== null - && organism.latinName !== "") - { - return organism.latinName; - } - // Then the trade name - if(organism.tradeName !== null - && organism.tradeName !== "") - { - return organism.tradeName; - } - // Then we give up - return gettext("Unnamed"); +function getLocalizedOrganismName(organism, language) { + var preferredLanguage = language || environment.currentLanguage; + // Fallback in case nothing works + if (organism === null) { + return gettext("Unnamed"); + } + // Attempting the following languages (in order): current language, default language, English + var languages = [preferredLanguage, environment.defaultLanguage, "en"]; + for (var j in languages) { + for (var i in organism.organismLocaleSet) { + var localeSet = organism.organismLocaleSet[i]; + //console.log(localeSet); + if (localeSet.organismLocalePK.locale == languages[j]) { + return localeSet.localName; + } + } + } + // Then we try the latin name + if (organism.latinName !== null + && organism.latinName !== "") { + return organism.latinName; + } + // Then the trade name + if (organism.tradeName !== null + && organism.tradeName !== "") { + return organism.tradeName; + } + // Then we give up + return gettext("Unnamed"); } /** @@ -116,30 +100,24 @@ function getLocalizedOrganismName(organism, language) * @param cropCategory * @returns {String} */ -function getLocalizedCropCategoryName(cropCategory) -{ +function getLocalizedCropCategoryName(cropCategory) { // Fallback in case nothing works - if(cropCategory === null) - { + if (cropCategory === null) { return "Unnamed"; } // Attempting the following languages (in order): current language, default language, English var languages = [environment.currentLanguage, environment.defaultLanguage, "en"]; - for(var j in languages) - { - for(var i in cropCategory.cropCategoryLocalSet) - { + for (var j in languages) { + for (var i in cropCategory.cropCategoryLocalSet) { var localeSet = cropCategory.cropCategoryLocalSet[i]; - if(localeSet.cropCategoryLocalPK.locale.trim() == languages[j].trim()) - { + if (localeSet.cropCategoryLocalPK.locale.trim() == languages[j].trim()) { return localeSet.localName; } } } // Then we try the latin name - if(cropCategory.defaultName !== null - && cropCategory.defaultName !== "") - { + if (cropCategory.defaultName !== null + && cropCategory.defaultName !== "") { return cropCategory.defaultName; } // Then we give up @@ -147,32 +125,28 @@ function getLocalizedCropCategoryName(cropCategory) } function getLocalizedOptionsHTML(optionsList) { - var translatedOptionsHTML = ""; - var languages = [environment.currentLanguage, environment.defaultLanguage, "en"]; - - for(var i in optionsList){ - var option = optionsList[i]; - var label = null; - var labelList = option.label; - for(var j in languages) - { - for(var k in labelList) - { - if(k === languages[j]) - { - label = labelList[k]; - break; - } + var translatedOptionsHTML = ""; + var languages = [environment.currentLanguage, environment.defaultLanguage, "en"]; + + for (var i in optionsList) { + var option = optionsList[i]; + var label = null; + var labelList = option.label; + for (var j in languages) { + for (var k in labelList) { + if (k === languages[j]) { + label = labelList[k]; + break; + } + } + if (label !== null) { + break; + } } - if(label !== null) - { - break; - } - } - translatedOptionsHTML += '<option value="' + option.value + '"' + (option.selected === "true" ? ' selected="selected"' : '') + ">" + label + "</option>"; - } - - return translatedOptionsHTML; + translatedOptionsHTML += '<option value="' + option.value + '"' + (option.selected === "true" ? ' selected="selected"' : '') + ">" + label + "</option>"; + } + + return translatedOptionsHTML; } /** @@ -182,24 +156,19 @@ function getLocalizedOptionsHTML(optionsList) { * @param {type} ambiguousValue * @returns {Number} */ -function getUnixTimestampFromJSON(ambiguousValue) -{ +function getUnixTimestampFromJSON(ambiguousValue) { var possibleDateObject; - if(!isMomentJSAvailable()) - { + if (!isMomentJSAvailable()) { possibleDateObject = new Date(ambiguousValue); console.info("Warning: Parsing date without MomentJS. Can't guarantee correct result."); } - else - { + else { possibleDateObject = moment(ambiguousValue).toDate(); } - if(possibleDateObject.getTime() === NaN && typeof parseInt(ambiguousValue) === "number") - { + if (possibleDateObject.getTime() === NaN && typeof parseInt(ambiguousValue) === "number") { return parseInt(ambiguousValue); } - else - { + else { return possibleDateObject.getTime(); } } @@ -209,15 +178,56 @@ function getUnixTimestampFromJSON(ambiguousValue) * * @return {Boolean} */ -function isMomentJSAvailable() -{ +function isMomentJSAvailable() { try { moment(); return true; } - catch (err) - { + catch (err) { return false; } -} \ No newline at end of file +} + +/** Converts numeric degrees to radians + * Essential to make the calculateDistanceBetweenCoordinates to work + **/ +if (typeof Number.prototype.toRad == 'undefined') { + Number.prototype.toRad = function () { + return this * Math.PI / 180; + } +} + + + +/** + * Reference: <a href="http://www.movable-type.co.uk/scripts/latlong.html">this webpage</a> + * <pre> + * This uses the "haversine" formula to calculate the great-circle distance between two points ? that is, the shortest distance over the earth?s surface ? giving an ?as-the-crow-flies? distance between the points (ignoring any hills, of course!). + Haversine formula: + a = sin²(?lat/2) + cos(lat1).cos(lat2).sin²(?long/2) + c = 2.atan2(?a, ?(1?a)) + d = R.c + where R is earth's radius (mean radius = 6,371km); + Note that angles need to be in radians to pass to trig functions! + * </pre> + * @param lat1 + * @param lon1 + * @param lat2 + * @param lon2 + * @return distance in km between to coordinates + */ +function calculateDistanceBetweenCoordinates(lat1, lon1, lat2, lon2) { + var R = 6371; // km + var dLat = (lat2 - lat1).toRad(); + var dLon = (lon2 - lon1).toRad(); + lat1 = lat1.toRad(); + lat2 = lat2.toRad(); + + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + var d = R * c; + + return d; +} diff --git a/src/main/webapp/templates/forecastConfigurationForm.ftl b/src/main/webapp/templates/forecastConfigurationForm.ftl index 0799bf43865af87052cf051d659562e3ed16abc4..d3a2e3104bd4625c60474abb2dbda70e822b388e 100755 --- a/src/main/webapp/templates/forecastConfigurationForm.ftl +++ b/src/main/webapp/templates/forecastConfigurationForm.ftl @@ -50,7 +50,8 @@ { "pointOfInterestId": "${poi.pointOfInterestId}", "name": "${poi.name?json_string}", - "pointOfInterestTypeId": ${poi.pointOfInterestTypeId} + "pointOfInterestTypeId": ${poi.pointOfInterestTypeId}, + "location": <#if poi.longitude?? && poi.latitude??>[${poi.longitude?c}, ${poi.latitude?c}]<#else>null</#if> }, </#list> ]; @@ -67,13 +68,19 @@ }; renderPoiSelect(selectLocationElement, locationList, selectedPoiId); - renderWeatherstationSelect(selectWeatherstationElement, locationList, selectedWeatherstationId); + // Setting weather station select list state correct on page load <#if isGridForecastSupported> handleUseGridWeatherDataClicked(document.getElementById("useGridWeatherData")<#if forecastConfiguration.weatherStationPointOfInterestId?has_content && forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId?has_content>,${forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId}</#if>); </#if> - function renderWeatherstationSelect(selectElement, elementList, selectedId) + /** + * @param selectElement the select list to render to + * @param elementList the list of weather stations to list + * @param selectedId the id (optional) to be preselected + * @param coordinateToMatch [lon, lat] coordinate to match. Optional. If set: The 3 closest weather stations are show at top of list, with distance to coordinate. + */ + window.renderWeatherstationSelect = function(selectElement, elementList, selectedId, coordinateToMatch) { selectElement.options.length = 0; @@ -94,16 +101,42 @@ )); // Add all locations that are weatherstations - elementList.forEach(poi => { - if(poi.pointOfInterestTypeId == POI_TYPE_WEATHERSTATION) - { - selectElement.appendChild(createOption( - poi.pointOfInterestId, - poi.name, - selectedId && poi.pointOfInterestId == selectedId - )); - } - }); + if(coordinateToMatch != null) + { + let stations = []; + elementList.forEach(poi => { + if(poi.pointOfInterestTypeId == POI_TYPE_WEATHERSTATION) + { + poi.distance = calculateDistanceBetweenCoordinates(poi.location[1], poi.location[0], coordinateToMatch[1], coordinateToMatch[0]).toFixed(1); + stations.push(poi); + } + }); + stations.sort(function(a,b){ + return a.distance - b.distance; + }); + stations.forEach(poi => { + selectElement.appendChild(createOption( + poi.pointOfInterestId, + poi.name + " (" + poi.distance +" km)", + selectedId && poi.pointOfInterestId == selectedId + )); + }); + + } + else + { + // Render all + elementList.forEach(poi => { + if(poi.pointOfInterestTypeId == POI_TYPE_WEATHERSTATION) + { + selectElement.appendChild(createOption( + poi.pointOfInterestId, + poi.name, + selectedId && poi.pointOfInterestId == selectedId + )); + } + }); + } } // Populate select list for point of interest -> NOT GENERAL! Default value is specific to location. @@ -127,6 +160,8 @@ }); } + renderWeatherstationSelect(selectWeatherstationElement, locationList, selectedWeatherstationId); + window.selectPoi = function(selectElement, selectedId) { if (selectedId) { const optionIndex = Array.from(selectElement.options).findIndex(option => option.value == selectedId); @@ -403,6 +438,7 @@ let handleLocationChanged = function(){ + let weatherstationSelect = document.getElementById("weatherStationPointOfInterestId"); // Which location has been selected? let selectedPoiId = document.getElementById("locationPointOfInterestId").options[document.getElementById("locationPointOfInterestId").selectedIndex].value; if(selectedPoiId <= 0) @@ -414,7 +450,6 @@ // Enable the weather datasource fieldset document.getElementById("weatherDatasourceFieldset").disabled=false; - let selectedLocation = undefined; for(let i=0; i<locationList.length;i++) { @@ -424,24 +459,22 @@ } } - <#if isGridForecastSupported> - - let gridCheckBox = document.getElementById("useGridWeatherData"); - gridCheckBox.checked = (selectedLocation.pointOfInterestTypeId != POI_TYPE_WEATHERSTATION); - handleUseGridWeatherDataClicked(gridCheckBox, (selectedLocation.pointOfInterestTypeId == POI_TYPE_WEATHERSTATION ? selectedLocation.pointOfInterestId: undefined)); - - <#else> - - weatherStationList = document.getElementById("weatherStationPointOfInterestId"); if(selectedLocation.pointOfInterestTypeId == POI_TYPE_WEATHERSTATION) { - selectPoi(weatherStationList, selectedLocation.pointOfInterestId); + renderWeatherstationSelect(weatherstationSelect,locationList, selectedLocation.pointOfInterestId, null); } else { - weatherStationList.selectedIndex = 0; + renderWeatherstationSelect(weatherstationSelect,locationList, null, selectedLocation.location); + weatherstationSelect.selectedIndex = 0; } - + + <#if isGridForecastSupported> + + let gridCheckBox = document.getElementById("useGridWeatherData"); + gridCheckBox.checked = (selectedLocation.pointOfInterestTypeId != POI_TYPE_WEATHERSTATION); + handleUseGridWeatherDataClicked(gridCheckBox, (selectedLocation.pointOfInterestTypeId == POI_TYPE_WEATHERSTATION ? selectedLocation.pointOfInterestId: undefined)); + </#if> } </script> @@ -538,9 +571,9 @@ <#if isGridForecastSupported> <div class="alert alert-info" role="alert">Velg sted ovenfor først, og velg deretter værdatakilde. Du kan enten velge en av de tilgjengelige værstasjonene, eller at ditt valgte steds plassering brukes til å hente værdata fra en ekstern tjeneste. Hvis ditt sted ligger nær - en av værstasjonene, gir det som oftest den beste kvaliteten på værdata.</div> + en av værstasjonene, gir det som oftest den beste kvaliteten på værdata. Hvis stedet du har valgt ikke er en værstasjon, vil værstasjonslista sorteres etter avstand til ditt sted.</div> <#else> - <div class="alert alert-info" role="alert">Velg sted ovenfor først, og velg deretter værdatakilde.</div> + <div class="alert alert-info" role="alert">Velg sted ovenfor først, og velg deretter værstasjon. Hvis stedet du har valgt ikke er en værstasjon, vil værstasjonslista sorteres etter avstand til ditt sted.</div> </#if> <fieldset id="weatherDatasourceFieldset" <#if !forecastConfiguration.weatherStationPointOfInterestId?has_content>disabled</#if>> <legend style="margin-bottom: 0px;">${i18nBundle.weatherDatasource}</legend>