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

Some new features. Mostly: Code cleanup and documentation

parent 10fe24cf
Branches
Tags
2 merge requests!22Develop,!18Spotit nordic map layers
...@@ -59,7 +59,8 @@ public class GrowthStageService { ...@@ -59,7 +59,8 @@ public class GrowthStageService {
public Response getDateForGrowthStage( public Response getDateForGrowthStage(
@PathParam("organismId") Integer organismId, @PathParam("organismId") Integer organismId,
@PathParam("growthStages") String growthStagesStr, @PathParam("growthStages") String growthStagesStr,
@QueryParam("location") String location @QueryParam("location") String location,
@QueryParam("season") Integer season
) )
{ {
//SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
...@@ -72,15 +73,15 @@ public class GrowthStageService { ...@@ -72,15 +73,15 @@ public class GrowthStageService {
if(growthStage.equals(32)) if(growthStage.equals(32))
{ {
gsDate.put(growthStage, LocalDate.of(LocalDate.now().getYear(), Month.JUNE,10)); gsDate.put(growthStage, LocalDate.of(season != null ? season : LocalDate.now().getYear(), Month.JUNE,10));
} }
else if(growthStage.equals(71)) else if(growthStage.equals(71))
{ {
gsDate.put(growthStage, LocalDate.of(LocalDate.now().getYear(), Month.JULY,25)); gsDate.put(growthStage, LocalDate.of(season != null ? season : LocalDate.now().getYear(), Month.JULY,25));
} }
else else
{ {
gsDate.put(growthStage, LocalDate.of(LocalDate.now().getYear(), Month.JANUARY,1)); gsDate.put(growthStage, LocalDate.of(season != null ? season : LocalDate.now().getYear(), Month.JANUARY,1));
} }
retVal.add(gsDate); retVal.add(gsDate);
} }
......
...@@ -64,7 +64,7 @@ along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. ...@@ -64,7 +64,7 @@ along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>.
} }
} }
#VIPSAttribution, .dateField, #layersField { #VIPSAttribution, .dateField, #layersField, #seasonField {
position: absolute; position: absolute;
z-index: 1000; z-index: 1000;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
...@@ -89,10 +89,14 @@ along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. ...@@ -89,10 +89,14 @@ along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>.
right: 10px; right: 10px;
} }
#layersField { #layersField, #seasonField {
left: 45px; left: 45px;
} }
#seasonField {
top: 150px;
}
#subMap1 .ol-attribution, #subMap2 .ol-attribution, #subMap3 .ol-attribution, #subMap4 .ol-attribution , #subMap1 .ol-attribution, #subMap2 .ol-attribution, #subMap3 .ol-attribution, #subMap4 .ol-attribution ,
#subMap1 .ol-zoom, #subMap2 .ol-zoom, #subMap3 .ol-zoom, #subMap4 .ol-zoom #subMap1 .ol-zoom, #subMap2 .ol-zoom, #subMap3 .ol-zoom, #subMap4 .ol-zoom
{ {
......
...@@ -37,16 +37,32 @@ for (var i in views) ...@@ -37,16 +37,32 @@ for (var i in views)
var maps = {mainMap:null, subMap1: null, subMap2: null, subMap3: null, subMap4: null}; var maps = {mainMap:null, subMap1: null, subMap2: null, subMap3: null, subMap4: null};
var language = "en"; var language = "en";
/**
* After all libraries have been loaded (added to the containing page), this
* function sets up all the maps and displays the initial view
* @returns {undefined}
*/
var initMap = function () var initMap = function ()
{ {
todayAtMidnight = getTodayAtMidnight(); todayAtMidnight = getTodayAtMidnight();
var nordicSeptoriaMapContainer = document.getElementById("nordicSeptoriaMapContainer"); var nordicSeptoriaMapContainer = document.getElementById("nordicSeptoriaMapContainer");
language = nordicSeptoriaMapContainer.getAttribute("data-language") != null ? nordicSeptoriaMapContainer.getAttribute("data-language") : language; // This is being used by dict, the translation tables
language = nordicSeptoriaMapContainer.getAttribute("data-language") !== null ? nordicSeptoriaMapContainer.getAttribute("data-language") : language;
// Giving the user the option to choose between the different views
var viewRadioList = ""; var viewRadioList = "";
for (var i in views) for (var i in views)
{ {
viewRadioList += " <input type='radio' name='selectedlayer' " + (views[i] == initialView ? "checked " : "") + "value='" + views[i] + "' onclick='showLayer(this.value);'/> " + geti18nText(views[i]) + "<br/>" viewRadioList += " <input type='radio' name='selectedlayer' " + (views[i] == initialView ? "checked " : "") + "value='" + views[i] + "' onclick='showLayer(this.value);'/> " + geti18nText(views[i]) + "<br/>"
} }
// Model results should be calculated by VIPS from 2014 and forward
var seasonList = "";
var currentSeason = new Date().getFullYear();
for(var season=2014;season<=currentSeason;season++)
{
seasonList += " <option value='" + season + "'" + (season == currentSeason ? " selected='selected'" : "") + ">" + season + "</option>";
}
// This HTML is injected into the hosting web page. It contains all of the
// maps and the controls
nordicSeptoriaMapContainer.innerHTML = "<div id='mainMap'>" nordicSeptoriaMapContainer.innerHTML = "<div id='mainMap'>"
+ " <div id='popupTooltip_mainMap' class='ol-popup'>" + " <div id='popupTooltip_mainMap' class='ol-popup'>"
+ " <a href='#' class='ol-popup-closer' onclick='closeOverlay(this);'></a>" + " <a href='#' class='ol-popup-closer' onclick='closeOverlay(this);'></a>"
...@@ -61,6 +77,13 @@ var initMap = function () ...@@ -61,6 +77,13 @@ var initMap = function ()
+ " <div id='layersField'>" + " <div id='layersField'>"
+ viewRadioList + viewRadioList
+ " </div>" + " </div>"
+ " <div id='seasonField'>"
+ " <select id='seasonList' name='season' onchange='changeSeason(this.options[this.options.selectedIndex].value);'>"
+ seasonList
+ " </select>"
+ " <input type='date' id='startDate' name='startDate' min='" + currentSeason + "-01-01' onchange='updateResults();'/> "
+ " <input type='date' id='endDate' name='endDate' max='" + currentSeason + "-12-31' onchange='updateResults();'/>"
+ " </div>"
+ " <div id='VIPSAttribution'>" + geti18nText("poweredBy") + " <a href='https://www.vips-landbruk.no/' target='new'><img id='VIPSLogo' src='" + hostName + "/public/nordic_septoria_map/logo_vips.png'/></a></div>" + " <div id='VIPSAttribution'>" + geti18nText("poweredBy") + " <a href='https://www.vips-landbruk.no/' target='new'><img id='VIPSLogo' src='" + hostName + "/public/nordic_septoria_map/logo_vips.png'/></a></div>"
+ "</div>" + "</div>"
+ "<div id='subMap1'>" + "<div id='subMap1'>"
...@@ -90,7 +113,7 @@ var initMap = function () ...@@ -90,7 +113,7 @@ var initMap = function ()
+ "<div id='popup'></div>" + "<div id='popup'></div>"
+ "</div>"; + "</div>";
// Initializing all the layers for all maps // Initializing all the layers (one for each model result view) for all maps
for (var i in views) for (var i in views)
{ {
for(var mapName in featureOverlays[views[i]]) for(var mapName in featureOverlays[views[i]])
...@@ -104,7 +127,7 @@ var initMap = function () ...@@ -104,7 +127,7 @@ var initMap = function ()
} }
} }
// Creating the 5 maps // Creating the 5 maps (present day + 4 days ahead)
for(var mapName in maps) for(var mapName in maps)
{ {
var currentMap = currentMap; var currentMap = currentMap;
...@@ -114,9 +137,9 @@ var initMap = function () ...@@ -114,9 +137,9 @@ var initMap = function ()
layers: [ layers: [
new ol.layer.Tile({ new ol.layer.Tile({
source: new ol.source.OSM() source: new ol.source.OSM()
})//, })
//featureOverlays["WHS"][mapName]
], ],
// This is the cartoon text bubble
overlays: [new ol.Overlay({ overlays: [new ol.Overlay({
element: document.getElementById('popupTooltip_' + mapName), element: document.getElementById('popupTooltip_' + mapName),
autoPan: true, autoPan: true,
...@@ -132,6 +155,7 @@ var initMap = function () ...@@ -132,6 +155,7 @@ var initMap = function ()
{ {
maps[mapName].addLayer(featureOverlays[views[i]][mapName]); maps[mapName].addLayer(featureOverlays[views[i]][mapName]);
} }
// When clicking on a feature - get the details and display
maps[mapName].on('singleclick', function(evt) { maps[mapName].on('singleclick', function(evt) {
var pixel = evt.map.getEventPixel(evt.originalEvent); var pixel = evt.map.getEventPixel(evt.originalEvent);
var coordinate = evt.coordinate; var coordinate = evt.coordinate;
...@@ -139,9 +163,63 @@ var initMap = function () ...@@ -139,9 +163,63 @@ var initMap = function ()
}); });
} }
showLayer(initialView); showLayer(initialView);
};
var updateResults = function() {
console.info("updateResults: TODO");
};
/**
*
* @returns {undefined}All features are removed. Used at when switching season
*/
var clearAll = function()
{
for (var i in views)
{
for(var mapName in featureOverlays[views[i]])
{
featureOverlays[views[i]][mapName].getSource().clear();
}
}
}
/**
* This function does what you think! It clears all current features
* and gets/displays results for the new selected season
*/
var changeSeason = function(selectedSeason)
{
// Clear layer(s)
clearAll();
// Set date field limits
var startDate = document.getElementById("startDate").value;
var endDate = document.getElementById("endDate").value;
document.getElementById("startDate").value =
document.getElementById("startDate").value != "" ?
selectedSeason + document.getElementById("startDate").value.substring(4) :
"";
document.getElementById("endDate").value =
document.getElementById("endDate").value != "" ?
selectedSeason + document.getElementById("endDate").value.substring(4) :
"";
// Get results for this season
getResults[getVisibleLayerName()](getCurrentSeason());
}; };
/**
* Which season is currently in use? Checking the season select list
*/
var getCurrentSeason = function()
{
return parseInt(document.getElementById('seasonList').options[document.getElementById('seasonList').options.selectedIndex].value);
}
/**
* Collects features at the point clicked on the map. Displays the feature
* properties (differs between views/models) in a cartoon text bubble
*/
var displayFeatureDetails = function(map, pixel, coordinate) var displayFeatureDetails = function(map, pixel, coordinate)
{ {
var features = []; var features = [];
...@@ -159,6 +237,9 @@ var displayFeatureDetails = function(map, pixel, coordinate) ...@@ -159,6 +237,9 @@ var displayFeatureDetails = function(map, pixel, coordinate)
currentOverlay.setPosition(coordinate); currentOverlay.setPosition(coordinate);
} }
/**
* Returns a text representation of the feature, including specific properties
*/
var getFeatureDetails = { var getFeatureDetails = {
"WHS": function(features) { return "TODO"; }, "WHS": function(features) { return "TODO"; },
"rainyDays": function(features) { "rainyDays": function(features) {
...@@ -167,6 +248,9 @@ var getFeatureDetails = { ...@@ -167,6 +248,9 @@ var getFeatureDetails = {
} }
}; };
/**
* Closing the cartoon text bubble (clicked feature details)
*/
var closeOverlay = function(theCloser) var closeOverlay = function(theCloser)
{ {
var currentOverlay = maps[theCloser.parentNode.id.split("_")[1]].getOverlays().item(0); var currentOverlay = maps[theCloser.parentNode.id.split("_")[1]].getOverlays().item(0);
...@@ -183,13 +267,12 @@ var closeOverlay = function(theCloser) ...@@ -183,13 +267,12 @@ var closeOverlay = function(theCloser)
*/ */
function showLayer(layerName) function showLayer(layerName)
{ {
//console.info("Attempting to show " + layerName);
// Has this layer been initialized already? // Has this layer been initialized already?
console.info("Attempting to show " + layerName);
if(featureOverlays[layerName]["mainMap"].getSource().getFeatures().length == 0) if(featureOverlays[layerName]["mainMap"].getSource().getFeatures().length == 0)
{ {
console.info("Layer " + layerName + " is new, need to get data "); //console.info("Layer " + layerName + " is new, need to get data ");
getResults[layerName](); getResults[layerName](getCurrentSeason());
} }
for(var mapName in maps) for(var mapName in maps)
{ {
...@@ -202,8 +285,9 @@ function showLayer(layerName) ...@@ -202,8 +285,9 @@ function showLayer(layerName)
} }
/** /**
* * Which layer is currently visible? I can tell you, using my immense powers
* @returns {String} * of deduction!
* @returns {String} The name of the currently visible layer
*/ */
var getVisibleLayerName = function() var getVisibleLayerName = function()
{ {
...@@ -220,7 +304,10 @@ var getVisibleLayerName = function() ...@@ -220,7 +304,10 @@ var getVisibleLayerName = function()
* Contains layer specific methods for fetching and displaying the data * Contains layer specific methods for fetching and displaying the data
*/ */
var getResults = { var getResults = {
"WHS" : function(){ /**
* TODO: This is old school
*/
"WHS" : function(season){
ajax(hostName + "/rest/forecastresults/-1000", function(e){ ajax(hostName + "/rest/forecastresults/-1000", function(e){
//ajax("http://vipslogic-local.no/rest/forecastresults/-1000", function(e){ //ajax("http://vipslogic-local.no/rest/forecastresults/-1000", function(e){
var results = JSON.parse(e.target.responseText); var results = JSON.parse(e.target.responseText);
...@@ -235,25 +322,22 @@ var getResults = { ...@@ -235,25 +322,22 @@ var getResults = {
} }
// This is here to fix an apparent bug in having Vector tiles // This is here to fix an apparent bug in having Vector tiles
// within the CSS grid system // within the CSS grid system
// THE FIRST LAYER THAT IS BEING SHOWN ON THE MAP MUST DO THIS.
// AT LEAST ONCE.
window.dispatchEvent(new Event('resize')); window.dispatchEvent(new Event('resize'));
}); });
}, },
"observedDisease": function() { console.info("NOT IMPLEMENTED");}, "observedDisease": function() { console.info("NOT IMPLEMENTED");},
"yieldLoss": function() { console.info("NOT IMPLEMENTED");}, "yieldLoss": function() { console.info("NOT IMPLEMENTED");},
"rainyDays": function() { "rainyDays": function(season) {
// Retrieve the forecast ids first // Retrieve the forecast ids first
ajax(hostName + "/rest/forecastconfigurations/model/RAINYDAYSM/2019", function(e){ ajax(hostName + "/rest/forecastconfigurations/model/RAINYDAYSM/" + season, function(e){
//console.info(e);
forecastsForSeason = JSON.parse(e.target.responseText); forecastsForSeason = JSON.parse(e.target.responseText);
// For each forecast config, get the results for a given season
// For each forecast config, get the results for a given period (GS32-GS71 or user selected) // and aggregate for a given period (GS32-GS71 or user selected)
// and aggregate
for(var i in forecastsForSeason) for(var i in forecastsForSeason)
{ {
// This is a closure. It has access to the parent function's variables. ajax(hostName + "/rest/forecastresults/" + forecastsForSeason[i].forecastConfigurationId + "/" + season + "-01-01/" + season + "-12-31", function(e){
// This is how we keep state in this chain of Ajax calls
var getForecastResults = function(e){
var forecastResults = JSON.parse(e.target.responseText); var forecastResults = JSON.parse(e.target.responseText);
//console.info(forecastResults); //console.info(forecastResults);
var currentForecastId = forecastResults[0].forecastConfigurationId; var currentForecastId = forecastResults[0].forecastConfigurationId;
...@@ -266,10 +350,13 @@ var getResults = { ...@@ -266,10 +350,13 @@ var getResults = {
break; break;
} }
} }
ajax(hostName + "/rest/gs/date/32,71/25/?season=" + season + "&location=" + currentForecast.locationPointOfInterestId.longitude + "," + currentForecast.locationPointOfInterestId.latitude, function(e){
var getForecastGSTimeLimit = function(e){ // This callback interprets the data returned from
// the VIPSLogic GrowthStageService
// It then forwards results and GS dates to displayResults
var GSResults = JSON.parse(e.target.responseText); var GSResults = JSON.parse(e.target.responseText);
var GS32Date, GS71Date; var GS32Date, GS71Date;
for(var i in GSResults) for(var i in GSResults)
{ {
if(GSResults[i]["32"] != null) if(GSResults[i]["32"] != null)
...@@ -281,14 +368,18 @@ var getResults = { ...@@ -281,14 +368,18 @@ var getResults = {
GS71Date = moment(GSResults[i]["71"]); GS71Date = moment(GSResults[i]["71"]);
} }
} }
var startDate = document.getElementById("startDate").value != "" ? moment(document.getElementById("startDate").value) : GS32Date;
var endDate = document.getElementById("endDate").value != "" ? moment(document.getElementById("endDate").value) : GS71Date;
//console.info("startDate=" + startDate.format("YYYY-MM-DD"));
// We now have everything we need to calculate the sum // We now have everything we need to calculate the sum
var rainyDaysSum = 0; var rainyDaysSum = 0;
for(var i in forecastResults) for(var i in forecastResults)
{ {
var validTimeStart = moment(forecastResults[i].validTimeStart); var validTimeStart = moment(forecastResults[i].validTimeStart);
if( if(
validTimeStart.isSameOrAfter(GS32Date) validTimeStart.isSameOrAfter(startDate)
&& validTimeStart.isSameOrBefore(GS71Date) && validTimeStart.isSameOrBefore(endDate)
&& forecastResults[i].allValues["RAINYDAYSM.RAINY_DAY"] == "true" && forecastResults[i].allValues["RAINYDAYSM.RAINY_DAY"] == "true"
) )
{ {
...@@ -296,21 +387,19 @@ var getResults = { ...@@ -296,21 +387,19 @@ var getResults = {
} }
} }
// We have the sum and location, let's display it on the map! // We have the sum and location, let's display it on the map!
// THIS IS THE LAST STEP IN THIS CHAIN!
displayResults["rainyDays"](rainyDaysSum, GS32Date, GS71Date, currentForecast); displayResults["rainyDays"](rainyDaysSum, GS32Date, GS71Date, currentForecast);
} });
ajax(hostName + "/rest/gs/date/32,71/25/?location=" + currentForecast.locationPointOfInterestId.longitude + "," + currentForecast.locationPointOfInterestId.latitude, getForecastGSTimeLimit); });
};
ajax(hostName + "/rest/forecastresults/" + forecastsForSeason[i].forecastConfigurationId + "/2019-05-01/2019-09-13", getForecastResults);
} }
}); });
}, },
"HM": function() { console.info("NOT IMPLEMENTED");}, "HM": function() { console.info("NOT IMPLEMENTED");},
}; };
// "observedDisease","yieldLoss","rainyDays","WHS","HM"
var featureZIndex = 10; var featureZIndex = 10;
// This is used by OpenLayers to define the features' looks
// The different models have varying thresholds, colors etc
var getFeatureStyle = { var getFeatureStyle = {
"WHS": function(feature) "WHS": function(feature)
{ {
...@@ -339,7 +428,6 @@ var getFeatureStyle = { ...@@ -339,7 +428,6 @@ var getFeatureStyle = {
}, },
"rainyDays": function(feature) "rainyDays": function(feature)
{ {
//console.info(feature);
var rainyDays = parseInt(feature.get("rainyDays")); var rainyDays = parseInt(feature.get("rainyDays"));
var color = rainyDays < 4 ? "green" : "red"; var color = rainyDays < 4 ? "green" : "red";
...@@ -362,14 +450,17 @@ var getFeatureStyle = { ...@@ -362,14 +450,17 @@ var getFeatureStyle = {
} }
}; };
/**
*
* Creating OpenLayers features from the model results from VIPSLogiv and
* displaying them on the map
*/
var displayResults = { var displayResults = {
"WHS" : function(results,mapName, date){ "WHS" : function(results,mapName, date){
var features = []; var features = [];
//var momentDate = moment(date); //var momentDate = moment(date);
for(var i in results) for(var i in results)
{ {
//console.info(moment(results[i].validTimeStart).format() + "==" + date.format());
if(moment(results[i].validTimeStart).isSame(date)){ if(moment(results[i].validTimeStart).isSame(date)){
//console.info(results[i].validGeometry.coordinates); //console.info(results[i].validGeometry.coordinates);
var feature = new ol.Feature({ var feature = new ol.Feature({
...@@ -380,8 +471,6 @@ var displayResults = { ...@@ -380,8 +471,6 @@ var displayResults = {
features.push(feature); features.push(feature);
} }
} }
//var featureSource = new ol.source.Vector({features:features});
//console.info(features);
featureOverlays["WHS"][mapName].getSource().clear(); featureOverlays["WHS"][mapName].getSource().clear();
featureOverlays["WHS"][mapName].getSource().addFeatures(features); featureOverlays["WHS"][mapName].getSource().addFeatures(features);
...@@ -405,6 +494,13 @@ var displayResults = { ...@@ -405,6 +494,13 @@ var displayResults = {
} }
}; };
/**
*
* @param {type} url
* @param {type} callback
* @returns {undefined}General XMLHttpRequest utility function. To avoid using JQuery or similarly
* bloated framework
*/
var ajax = function(url, callback) var ajax = function(url, callback)
{ {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
...@@ -413,6 +509,9 @@ var ajax = function(url, callback) ...@@ -413,6 +509,9 @@ var ajax = function(url, callback)
xhr.send(); xhr.send();
}; };
/**
* Simple popup with detailed description of each model
*/
var showModelInfo = { var showModelInfo = {
"WHS": function() "WHS": function()
{ {
...@@ -440,6 +539,10 @@ var hideModelInfo = function() ...@@ -440,6 +539,10 @@ var hideModelInfo = function()
document.getElementById('popup').style.display="none"; document.getElementById('popup').style.display="none";
} }
/**
* TODO: this is using Europe/Oslo as default. What about Finland/the Baltics?
* @returns {getTodayAtMidnight.today}
*/
function getTodayAtMidnight() function getTodayAtMidnight()
{ {
var timeZone="Europe/Oslo"; var timeZone="Europe/Oslo";
...@@ -475,7 +578,7 @@ var getLayerLegend = { ...@@ -475,7 +578,7 @@ var getLayerLegend = {
// All the stuff below is for dynamically loading all JavaScript Libs that are // All the stuff below is for dynamically loading all JavaScript Libs that are
// needed to run the application // needed to run the application
// After the client document has finished loading, we download OpenLayers and subsequently // After the client document has finished loading, we download OpenLayers, MomentJS and subsequently
// initialize the map. // initialize the map.
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
// Some introspection here // Some introspection here
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment