Skip to content
Snippets Groups Projects
Commit 81bad0c0 authored by Tor-Einar Skog's avatar Tor-Einar Skog
Browse files

feat: sort weatherstations by distance to POI

parent a7117993
No related branches found
No related tags found
1 merge request!191Add map module and Open-Meteo support
......@@ -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;
}
......@@ -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>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment