diff --git a/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchModelService.java b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchModelService.java index fcdb61a82d05543395ed9e88432c817adff02688..839c93cf0ac2375aeee6e0f779b428e7ae411606 100644 --- a/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchModelService.java +++ b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchModelService.java @@ -143,6 +143,46 @@ public class WheatLeafBlotchModelService { } } + /** + * Returns the yield loss from Septoria in wheat(?) + * @param organizationId + * @param season + * @return + */ + @GET + @Path("yieldloss/septoria/{organizationId}/{season}") + @Produces("application/json;charset=UTF-8") + public Response getYieldLoss( + @PathParam("organizationId") Integer organizationId, + @PathParam("season") Integer season + ) + { + List<YieldLoss> retVal; + if(organizationId <= 0) + { + retVal = em.createNamedQuery("YieldLoss.findBySeason") + .setParameter("season", season) + .getResultList(); + } + else + { + retVal = em.createNativeQuery("SELECT * FROM wheatleafb.yield_loss \n" + + "WHERE season = :season\n" + + "AND point_of_interest_id IN (\n" + + " SELECT point_of_interest_id FROM point_of_interest\n" + + " WHERE user_id IN (SELECT user_id FROM vips_logic_user WHERE organization_id = :organizationId" + + " )\n" + + ")" + , + YieldLoss.class + ) + .setParameter("organizationId", organizationId) + .setParameter("season", season) + .getResultList(); + } + return Response.ok().entity(retVal).build(); + } + @GET @Path("runmodel/{organizationId}") @Produces("application/json;charset=UTF-8") diff --git a/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/YieldLoss.java b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/YieldLoss.java new file mode 100644 index 0000000000000000000000000000000000000000..461c35fc335f4665dd3c00cbc334b7b45fa89323 --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/YieldLoss.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.logic.modules.wheatleafblotch; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.xml.bind.annotation.XmlRootElement; +import no.nibio.vips.logic.entity.PointOfInterest; + +/** + * @copyright 2019 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +@Entity +@Table(name = "yield_loss", schema = "wheatleafb") +@XmlRootElement +@NamedQueries({ + @NamedQuery(name = "YieldLoss.findAll", query = "SELECT y FROM YieldLoss y"), + @NamedQuery(name = "YieldLoss.findBySeason", query = "SELECT y FROM YieldLoss y WHERE y.yieldLossPK.season = :season"), + @NamedQuery(name = "YieldLoss.findByPointOfInterestId", query = "SELECT y FROM YieldLoss y WHERE y.yieldLossPK.pointOfInterestId = :pointOfInterestId"), + @NamedQuery(name = "YieldLoss.findByYieldLoss", query = "SELECT y FROM YieldLoss y WHERE y.yieldLoss = :yieldLoss") +}) +public class YieldLoss implements Serializable { + + private static final long serialVersionUID = 1L; + @EmbeddedId + protected YieldLossPK yieldLossPK; + @Column(name = "yield_loss") + private Double yieldLoss; + + public YieldLoss() { + } + + public YieldLoss(YieldLossPK yieldLossPK) { + this.yieldLossPK = yieldLossPK; + } + + public YieldLoss(int season, PointOfInterest pointOfInterestId) { + this.yieldLossPK = new YieldLossPK(season, pointOfInterestId); + } + + public YieldLossPK getYieldLossPK() { + return yieldLossPK; + } + + public void setYieldLossPK(YieldLossPK yieldLossPK) { + this.yieldLossPK = yieldLossPK; + } + + public Double getYieldLoss() { + return yieldLoss; + } + + public void setYieldLoss(Double yieldLoss) { + this.yieldLoss = yieldLoss; + } + + @Override + public int hashCode() { + int hash = 0; + hash += (yieldLossPK != null ? yieldLossPK.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object object) { + // TODO: Warning - this method won't work in the case the id fields are not set + if (!(object instanceof YieldLoss)) { + return false; + } + YieldLoss other = (YieldLoss) object; + if ((this.yieldLossPK == null && other.yieldLossPK != null) || (this.yieldLossPK != null && !this.yieldLossPK.equals(other.yieldLossPK))) { + return false; + } + return true; + } + + @Override + public String toString() { + return "no.nibio.vips.logic.modules.wheatleafblotch.YieldLoss[ yieldLossPK=" + yieldLossPK + " ]"; + } + +} diff --git a/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/YieldLossPK.java b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/YieldLossPK.java new file mode 100644 index 0000000000000000000000000000000000000000..c79cddca8cf4f56be2bd6ee249be1cc4aadb4616 --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/YieldLossPK.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.logic.modules.wheatleafblotch; + +import java.io.Serializable; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.validation.constraints.NotNull; +import no.nibio.vips.logic.entity.PointOfInterest; + +/** + * @copyright 2019 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +@Embeddable +public class YieldLossPK implements Serializable { + + @Basic(optional = false) + @NotNull + @Column(name = "season") + private int season; + @Basic(optional = false) + @NotNull + @JoinColumn(name = "point_of_interest_id", referencedColumnName = "point_of_interest_id") + @ManyToOne + private PointOfInterest pointOfInterestId; + + public YieldLossPK() { + } + + public YieldLossPK(int season, PointOfInterest pointOfInterestId) { + this.season = season; + this.pointOfInterestId = pointOfInterestId; + } + + public int getSeason() { + return season; + } + + public void setSeason(int season) { + this.season = season; + } + + public PointOfInterest getPointOfInterestId() { + return pointOfInterestId; + } + + public void setPointOfInterestId(PointOfInterest pointOfInterestId) { + this.pointOfInterestId = pointOfInterestId; + } + + @Override + public int hashCode() { + int hash = 0; + hash += (int) season; + hash += (int) pointOfInterestId.getPointOfInterestId(); + return hash; + } + + @Override + public boolean equals(Object object) { + // TODO: Warning - this method won't work in the case the id fields are not set + if (!(object instanceof YieldLossPK)) { + return false; + } + YieldLossPK other = (YieldLossPK) object; + if (this.season != other.season) { + return false; + } + if (this.pointOfInterestId != other.pointOfInterestId) { + return false; + } + return true; + } + + @Override + public String toString() { + return "no.nibio.vips.logic.modules.wheatleafblotch.YieldLossPK[ season=" + season + ", pointOfInterestId=" + pointOfInterestId + " ]"; + } + +} diff --git a/src/main/setup/jboss-ds.xml b/src/main/setup/jboss-ds.xml index 7efd3dba56f87b794258307a8ed27c7042ae6042..67a462f0d033a6d4c6b3dc511adb594f3c41eca1 100755 --- a/src/main/setup/jboss-ds.xml +++ b/src/main/setup/jboss-ds.xml @@ -20,6 +20,16 @@ <max-pool-size>20</max-pool-size> <idle-timeout-minutes>5</idle-timeout-minutes> </local-tx-datasource> + <local-tx-datasource> + <jndi-name>wheatleafb</jndi-name> + <connection-url>jdbc:postgresql://localhost:5432/vipslogic?searchpath=wheatleafb</connection-url> + <driver-class>org.postgresql.Driver</driver-class> + <user-name>vipslogic</user-name> + <password>VIPS123</password> + <min-pool-size>5</min-pool-size> + <max-pool-size>20</max-pool-size> + <idle-timeout-minutes>5</idle-timeout-minutes> + </local-tx-datasource> <local-tx-datasource> <jndi-name>MessagingSchema</jndi-name> <connection-url>jdbc:postgresql://localhost/vipslogic</connection-url> diff --git a/src/main/webapp/public/nordic_septoria_map/nordic_septoria_map.css b/src/main/webapp/public/nordic_septoria_map/nordic_septoria_map.css index 1ce9a56a3eaf8219481f5a076421d73b7471308d..14724354689e9cf763fb0791073ead493769ee78 100644 --- a/src/main/webapp/public/nordic_septoria_map/nordic_septoria_map.css +++ b/src/main/webapp/public/nordic_septoria_map/nordic_septoria_map.css @@ -76,11 +76,16 @@ along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. left: 10px; } -.dateField, #layersField { - border-radius: 5px; +.dateField, #layersField, #seasonField { + border-radius: 5px; padding: 5px; - top: 10px; background-color: white; + +} + +.dateField, #layersField { + + top: 10px; font-weight: bold; } @@ -89,14 +94,39 @@ along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. right: 10px; } +.dateField.hidden { + display: none; +} + #layersField, #seasonField { left: 45px; } -#seasonField { - top: 150px; +#layersField input[type="radio"], #seasonField input[type="date"], #seasonField select +{ + margin: 0; + font-size: small; + /*margin: initial; + height: initial; + border-radius: initial; + box-shadow: initial; + font-size: initial; + transition: initial;*/ } +#seasonField { + + top: 125px; + max-width: 170px; + border: 1px solid #999; + background-color: transparent; + /*font-size: 1em;*/ +} +#seasonList, #startDate { + margin-bottom: 5px; +} + + #subMap1 .ol-attribution, #subMap2 .ol-attribution, #subMap3 .ol-attribution, #subMap4 .ol-attribution , #subMap1 .ol-zoom, #subMap2 .ol-zoom, #subMap3 .ol-zoom, #subMap4 .ol-zoom { @@ -124,6 +154,7 @@ along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. font-family: Arial, Helvetica, sans-serif; font-size: small; } + /* For the feature info popup*/ .ol-popup { position: absolute; @@ -159,6 +190,7 @@ along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. margin-left: -11px; } .ol-popup-closer { + cursor: pointer; text-decoration: none; position: absolute; top: 2px; diff --git a/src/main/webapp/public/nordic_septoria_map/nordic_septoria_map.js b/src/main/webapp/public/nordic_septoria_map/nordic_septoria_map.js index 02f0ba9c8ad6065d24813385ec9c05790c426570..1187008102e0bd6653f11ac8123e8a1498a0d23f 100644 --- a/src/main/webapp/public/nordic_septoria_map/nordic_septoria_map.js +++ b/src/main/webapp/public/nordic_septoria_map/nordic_septoria_map.js @@ -71,7 +71,7 @@ var initMap = function () // maps and the controls nordicSeptoriaMapContainer.innerHTML = "<div id='mainMap'>" + " <div id='popupTooltip_mainMap' class='ol-popup'>" - + " <a href='#' class='ol-popup-closer' onclick='closeOverlay(this);'></a>" + + " <span class='ol-popup-closer' onclick='closeOverlay(this);'></span>" + " <div></div>" + " </div>" + " <div class='dateField'>" @@ -86,8 +86,8 @@ var initMap = function () + " <div id='seasonField'>" + " <select id='seasonList' name='season' onchange='changeSeason(this.options[this.options.selectedIndex].value);'>" + seasonList - + " </select><br/>" - + " <input type='date' id='startDate' name='startDate' min='" + currentSeason + "-01-01' onchange='updateResults();'/><br/>" + + " </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>" @@ -118,7 +118,11 @@ var initMap = function () + "<div id='subMap4DateField' class='dateField'></div></div>" + "<div id='popup'></div>" + "</div>"; - + + //console.info("areWeInSeason() ? " + areWeInSeason()); + toggleLegendVisibility(areWeInSeason()); + toggleSubMapsVisibility(areWeInSeason()); + // Initializing all the layers (one for each model result view) for all maps for (var i in views) { @@ -171,6 +175,52 @@ var initMap = function () showLayer(initialView); }; +var toggleLegendVisibility = function(visible) +{ + var dateFieldEls = document.getElementsByClassName("dateField"); + for(var i=0; i< dateFieldEls.length;i++) + { + if(visible) + { + dateFieldEls[i].classList.remove("hidden"); + } + else + { + dateFieldEls[i].classList.add("hidden"); + } + } +} + +var toggleSubMapsVisibility = function(visible) +{ + for(var i=1;i<=4;i++) + { + var subMapDiv = document.getElementById("subMap" + i); + subMapDiv.style.display= visible ? "block":"none"; + } +}; + +// Investigates if NOW is within the latest growing season +// (arbitrarily decided to be XXXX-03-01 - XXXX-10-01) +// And the selected season is the current season +function areWeInSeason() +{ + var thisYear = new Date().getFullYear(); + if(getCurrentSeason() === thisYear) + { + // Active season is arbitrarily decided to be XXXX-03-01 - XXXX-10-01 + // Months are zero based + var seasonStart = new Date(thisYear, 2, 1); + var seasonEnd = new Date(thisYear, 9, 1); + var now = new Date(); + return now >= seasonStart && now <= seasonEnd; + } + else + { + return false; + } +} + var updateResults = function() { var startDate = document.getElementById('startDate').value !== "" ? moment(document.getElementById('startDate').value) : null; var endDate = document.getElementById('endDate').value !== "" ? moment(document.getElementById('endDate').value) : null; @@ -234,6 +284,8 @@ var clearAll = function() */ var changeSeason = function(selectedSeason) { + toggleLegendVisibility(areWeInSeason()); + toggleSubMapsVisibility(areWeInSeason()); // Clear layer(s) clearAll(); // Clear cache @@ -299,6 +351,12 @@ var getFeatureDetails = { "HM": function(features) { var properties = features[0].getProperties(); return "GS 32 estimated at " + properties.GS32Date.format("YYYY-MM-DD") + ", GS 71 estimated at " + properties.GS71Date.format("YYYY-MM-DD"); + }, + "observedDisease": function(features){ + return ""; + }, + "yieldLoss": function(features){ + return ""; } }; @@ -431,8 +489,25 @@ var getResults = { "WHS" : function(season){ getResultsChain(season, "WHS"); }, - "observedDisease": function() { console.info("NOT IMPLEMENTED");}, - "yieldLoss": function() { console.info("NOT IMPLEMENTED");}, + "observedDisease": function(season) { + ajax(hostName + "/rest/observation/filter/1?pestId=123&cropId=70&from=" + season + "-12-30&to=" + (season + 1) + "-01-01", function(e){ + var observations = JSON.parse(e.target.responseText); + for(var i in observations) + { + displayResults["observedDisease"](observations[i]); + } + } + ); + + }, + "yieldLoss": function(season) { ajax(hostName + "/rest/wheatleafblotchmodel/yieldloss/septoria/-1/" + season, function(e){ + var yieldLosses = JSON.parse(e.target.responseText); + for(var i in yieldLosses) + { + displayResults["yieldLoss"](yieldLosses[i]); + } + } + ); }, "rainyDays": function(season) { getResultsChain(season, "rainyDays"); }, @@ -564,6 +639,66 @@ var getFeatureStyle = { zIndex: featureZIndex++ }) ]; + }, + "observedDisease": function(feature){ + var color = "green"; + var observedDisease = parseFloat(feature.get("observedDisease")); + if(observedDisease >=0.5) + { + color = "yellow"; + } + if(observedDisease >= 5) + { + color = "orange"; + } + if(observedDisease >= 10) + { + color = "red"; + } + if(observedDisease >= 50) + { + color = "#990000"; + } + + return [ + new ol.style.Style({ + image: new ol.style.Circle({ + fill: new ol.style.Fill({ color: color }), + stroke: new ol.style.Stroke({ color: [0,0,0,0], width: 0 }), + radius: 11 + }), + text: new ol.style.Text({ + text: feature.get("observedDisease"), + font: 'bold 12px sans-serif', + fill: new ol.style.Fill({ color: "white" }), + stroke: new ol.style.Stroke({ color: [90,90,90,1], width: 1 }) + }), + zIndex: featureZIndex++ + }) + ]; + }, + "yieldLoss" : function(feature){ + var color = "green"; + //var radius = 11 + (Math.min(2,feature.get("yieldLoss").length - 2)) * 4; + var radius = 2; + // TODO: Add threshold! + return [ + new ol.style.Style({ + image: new ol.style.Circle({ + fill: new ol.style.Fill({ color: color }), + stroke: new ol.style.Stroke({ color: [0,0,0,0], width: 0 }), + //radius: 11 + radius: radius + }), + text: new ol.style.Text({ + text: feature.get("yieldLoss"), + font: 'bold 12px sans-serif', + fill: new ol.style.Fill({ color: "white" }), + stroke: new ol.style.Stroke({ color: [90,90,90,1], width: 1 }) + }), + zIndex: featureZIndex++ + }) + ]; } }; @@ -607,6 +742,29 @@ var displayResults = { var features = []; features.push(feature); featureOverlays["HM"]["mainMap"].getSource().addFeatures(features); + }, + "observedDisease": function(observation){ + var observationData = JSON.parse(observation.observationData); + + //console.info(JSON.parse(observation.observationData)); + var feature = new ol.Feature({ + geometry: new ol.geom.Point(ol.proj.fromLonLat([observation.location.longitude, observation.location.latitude])), + observedDisease: observationData.number.toString() + }); + var features = []; + features.push(feature); + featureOverlays["observedDisease"]["mainMap"].getSource().addFeatures(features); + }, + "yieldLoss": function(yieldLoss){ + //console.info(yieldLoss); + var location = yieldLoss.yieldLossPK.pointOfInterestId; + var feature = new ol.Feature({ + geometry: new ol.geom.Point(ol.proj.fromLonLat([location.longitude, location.latitude])), + yieldLoss: yieldLoss.yieldLoss.toString() + }); + var features = []; + features.push(feature); + featureOverlays["yieldLoss"]["mainMap"].getSource().addFeatures(features); } }; @@ -689,8 +847,14 @@ var getLayerLegend = { }, "HM": function(){ return "TODO"; + }, + "observedDisease" : function(){ + return "TODO"; + }, + "yieldLoss" : function(){ + return "TODO"; } -}; +}; // ########################################################################### @@ -773,7 +937,7 @@ var loadScript = function(url, callback) script.onreadystatechange = callback; script.onload = callback; - console.info(script); + //console.info(script); document.head.appendChild(script); };