From 2e4dd379fcb38ea610e0b0d06c4d9628db0deb5d Mon Sep 17 00:00:00 2001
From: Tor-Einar Skog <tor-einar.skog@nibio.no>
Date: Wed, 19 Dec 2018 10:48:27 +0100
Subject: [PATCH] Adding Leaf Blotch Model Specific stuff

---
 .../logic/controller/session/MessageBean.java |   2 +-
 .../controller/session/ObservationBean.java   |   4 +-
 .../wheatleafblotch/ResistanceFactor.java     | 121 +++++++
 .../WheatLeafBlotchModelService.java          | 324 ++++++++++++++++++
 ...heatLeafBlotchPreparationEffectFactor.java | 113 ++++++
 src/main/webapp/js/observationMap.js          |   4 +-
 6 files changed, 563 insertions(+), 5 deletions(-)
 create mode 100644 src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/ResistanceFactor.java
 create mode 100644 src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchModelService.java
 create mode 100644 src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchPreparationEffectFactor.java

diff --git a/src/main/java/no/nibio/vips/logic/controller/session/MessageBean.java b/src/main/java/no/nibio/vips/logic/controller/session/MessageBean.java
index 64b0bce0..e0d08001 100755
--- a/src/main/java/no/nibio/vips/logic/controller/session/MessageBean.java
+++ b/src/main/java/no/nibio/vips/logic/controller/session/MessageBean.java
@@ -434,6 +434,6 @@ public class MessageBean {
             return new ArrayList<>();
         }
     }
-    
+
     
 }
diff --git a/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java b/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java
index 84296265..09683391 100755
--- a/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java
+++ b/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 NIBIO <http://www.nibio.no/>. 
+ * Copyright (c) 2018 NIBIO <http://www.nibio.no/>. 
  * 
  * This file is part of VIPSLogic.
  * VIPSLogic is free software: you can redistribute it and/or modify
@@ -58,7 +58,7 @@ import org.wololo.geojson.FeatureCollection;
 import org.wololo.geojson.GeoJSONFactory;
 
 /**
- * @copyright 2014-2015 <a href="http://www.nibio.no/">NIBIO</a>
+ * @copyright 2014-2018 <a href="http://www.nibio.no/">NIBIO</a>
  * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
  */
 @Stateless
diff --git a/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/ResistanceFactor.java b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/ResistanceFactor.java
new file mode 100644
index 00000000..175f13c9
--- /dev/null
+++ b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/ResistanceFactor.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2015 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 java.math.BigDecimal;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @copyright 2018 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+@Entity
+@Table(name = "resistance_factor", schema = "wheatleafb")
+@XmlRootElement
+@NamedQueries({
+    @NamedQuery(name = "ResistanceFactor.findAll", query = "SELECT f FROM Factors f"),
+    @NamedQuery(name = "ResistanceFactor.findByOrganizationId", query = "SELECT f FROM ResistanceFactor f where f.organizationId IN (SELECT o.organizationId FROM Organization o WHERE o = :organizationId OR o.parentOrganizationId = :organizationId)"),
+    @NamedQuery(name = "ResistanceFactor.findByCropOrganismId", query = "SELECT f FROM ResistanceFactor f WHERE f.cropOrganismId = :cropOrganismId"),
+    @NamedQuery(name = "ResistanceFactor.findByResistanceFactor", query = "SELECT f FROM ResistanceFactor f WHERE f.resistanceFactor = :resistanceFactor")
+})
+public class ResistanceFactor implements Serializable {
+    private static final long serialVersionUID = 1L;
+    @Id
+    @Basic(optional = false)
+    @NotNull
+    @Column(name = "crop_organism_id")
+    private Integer cropOrganismId;
+    @Column(name = "organization_id")
+    private Integer organizationId;
+    @Column(name = "resistance_factor")
+    private BigDecimal resistanceFactor;
+
+    public ResistanceFactor() {
+    }
+
+    public ResistanceFactor(Integer cropOrganismId) {
+        this.cropOrganismId = cropOrganismId;
+    }
+
+    public Integer getCropOrganismId() {
+        return cropOrganismId;
+    }
+
+    public void setCropOrganismId(Integer cropOrganismId) {
+        this.cropOrganismId = cropOrganismId;
+    }
+
+    public BigDecimal getResistanceFactor() {
+        return resistanceFactor;
+    }
+
+    public void setResistanceFactor(BigDecimal resistanceFactor) {
+        this.resistanceFactor = resistanceFactor;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 0;
+        hash += (cropOrganismId != null ? cropOrganismId.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 ResistanceFactor)) {
+            return false;
+        }
+        ResistanceFactor other = (ResistanceFactor) object;
+        if ((this.cropOrganismId == null && other.cropOrganismId != null) || (this.cropOrganismId != null && !this.cropOrganismId.equals(other.cropOrganismId))) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "no.nibio.vips.logic.modules.barleynetblotch.Factors[ cropOrganismId=" + cropOrganismId + " ]";
+    }
+
+    /**
+     * @return the organizationId
+     */
+    public Integer getOrganizationId() {
+        return organizationId;
+    }
+
+    /**
+     * @param organizationId the organizationId to set
+     */
+    public void setOrganizationId(Integer organizationId) {
+        this.organizationId = organizationId;
+    }
+
+}
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
new file mode 100644
index 00000000..fcdb61a8
--- /dev/null
+++ b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchModelService.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2015 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 com.webcohesion.enunciate.metadata.Facet;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.PersistenceContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import no.nibio.vips.entity.ModelConfiguration;
+import no.nibio.vips.entity.Result;
+import no.nibio.vips.entity.WeatherObservation;
+import no.nibio.vips.logic.entity.Organism;
+import no.nibio.vips.logic.entity.Organization;
+import no.nibio.vips.logic.entity.PointOfInterestWeatherStation;
+import no.nibio.vips.logic.entity.Preparation;
+import no.nibio.vips.logic.util.RunModelException;
+import no.nibio.vips.logic.util.SessionControllerGetter;
+import no.nibio.vips.logic.util.SystemTime;
+import no.nibio.vips.observation.ObservationImpl;
+import no.nibio.vips.pestmanagement.SprayingImpl;
+import no.nibio.vips.util.ParseRESTParamUtil;
+import no.nibio.vips.util.WeatherElements;
+import no.nibio.vips.util.weather.WeatherDataSourceException;
+import no.nibio.vips.util.weather.WeatherDataSourceUtil;
+
+/**
+ * @copyright 2018 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+@Path("rest/wheatleafblotchmodel")
+@Facet("restricted")
+public class WheatLeafBlotchModelService {
+    private final static String VIPSCOREMANAGER_URL = System.getProperty("no.nibio.vips.logic.VIPSCOREMANAGER_URL");
+    
+    @PersistenceContext(unitName="VIPSLogic-PU")
+    EntityManager em;
+
+    @GET
+    @Path("wheatvarieties/{organizationId}")
+    @Produces("application/json;charset=UTF-8")
+    public Response getWheatVarieties(@PathParam("organizationId") Integer organizationId)
+    {
+        try
+        {
+            Organization o = em.find(Organization.class, organizationId);
+            List<ResistanceFactor> resistanceFactors = em.createNamedQuery("ResistanceFactor.findByOrganizationId")
+                .setParameter("organizationId", o).getResultList();
+            
+            List<Organism> wheatTypes = em.createNativeQuery("SELECT * FROM public.organism WHERE parent_organism_id IN ("
+                    + "SELECT organism_id FROM public.organism WHERE latin_name='Triticum'"
+                    + ")", Organism.class).getResultList();
+            
+            List<Integer> varietyIds = new ArrayList<>();
+            for(ResistanceFactor f:resistanceFactors)
+            {
+                varietyIds.add(f.getCropOrganismId());
+            }
+            
+            List<Organism> wheatVarieties = new ArrayList<>();
+            
+            if(varietyIds.size() > 0)
+            {
+                wheatVarieties = em.createNamedQuery("Organism.findByOrganismIds")
+                        .setParameter("organismIds", varietyIds).getResultList();
+                for(Organism variety:wheatVarieties)
+                {
+                    for(ResistanceFactor f:resistanceFactors)
+                    {
+                        if(f.getCropOrganismId().equals(variety.getOrganismId()))
+                        {
+                            variety.getExtraProperties().put("factors", f);
+                        }
+                    }
+                    for(Organism type:wheatTypes)
+                    {
+                        if(variety.getParentOrganismId().equals(type.getOrganismId()))
+                        {
+                            variety.getExtraProperties().put("type", type);
+                        }
+                    }
+                }
+            }
+            return Response.ok().entity(wheatVarieties).build();
+        }
+        catch(NoResultException ex)
+        {
+            return Response.ok().entity(ex.getMessage()).build();
+        }
+        
+    }
+    
+    @GET
+    @Path("preparations/{organizationId}")
+    @Produces("application/json;charset=UTF-8")
+    public Response getPreparations(@PathParam("organizationId") Integer organizationId)
+    {
+        try
+        {
+            // TODO: Get all organization children as well
+            // Must get the preparations first
+            List<Preparation> preparations = em.createNativeQuery(
+                            "SELECT * FROM preparation "
+                            + "WHERE organization_id = :organizationId "
+                            + "AND preparation_id IN ("
+                                    + "SELECT preparation_id FROM wheatleafb.wheatleafblotch_preparation_effect_factor"
+                            + ")",
+                    Preparation.class)
+                    .setParameter("organizationId", organizationId)
+                    .getResultList();
+            return Response.ok().entity(preparations).build();
+        }
+        catch(NoResultException ex)
+        {
+            return Response.ok().entity(ex.getMessage()).build();
+        }
+    }
+    
+    @GET
+    @Path("runmodel/{organizationId}")
+    @Produces("application/json;charset=UTF-8")
+    public Response runModel(
+            @PathParam("organizationId") Integer organizationId,
+            @QueryParam("wheatType") Integer wheatType,
+            @QueryParam("timeZone") String timeZoneStr,
+            @QueryParam("weatherStationId") Integer weatherStationId,
+            @QueryParam("sowingDate") String sowingDateStr,
+            @QueryParam("cropId") Integer cropOrganismId,
+            @QueryParam("observationDate") String observationDateStr,
+            @QueryParam("observationValue") String observationValueStr,
+            @QueryParam("sameCropAsLastSeason") String sameCropAsLastSeasonStr,
+            @QueryParam("plowed") String plowedStr,
+            @QueryParam("sprayingDate") String sprayingDateStr,
+            @QueryParam("preparationId1") String preparationId1Str,
+            @QueryParam("preparationDose1") String preparationDose1Str,
+            @QueryParam("preparationId2") String preparationId2Str,
+            @QueryParam("preparationDose2") String preparationDose2Str,
+            @QueryParam("preparationId3") String preparationId3Str,
+            @QueryParam("preparationDose3") String preparationDose3Str
+    )
+    {
+        // Some parsing needed...
+        ParseRESTParamUtil parseUtil = new ParseRESTParamUtil();
+        TimeZone timeZone = TimeZone.getTimeZone(timeZoneStr);
+        Date sowingDate = parseUtil.parseISODate(sowingDateStr, timeZone);
+        Date observationDate = parseUtil.parseISODateTime(observationDateStr, timeZone);
+        // In case it's just a date string
+        if(observationDate == null)
+        {
+            observationDate = parseUtil.parseISODate(observationDateStr, timeZone);
+        }
+        Double observationValue = parseUtil.parseDouble(observationValueStr);
+        Boolean sameCropAsLastSeason = parseUtil.parseCheckbox(sameCropAsLastSeasonStr);
+        Boolean plowed =  parseUtil.parseCheckbox(plowedStr);
+        Date sprayingDate = parseUtil.parseISODate(sprayingDateStr, timeZone);
+        Integer preparationId1 = parseUtil.parseInteger(preparationId1Str);
+        Double preparationDose1 = parseUtil.parseDouble(preparationDose1Str);
+        Integer preparationId2 = parseUtil.parseInteger(preparationId2Str);
+        Double preparationDose2 = parseUtil.parseDouble(preparationDose2Str);
+        Integer preparationId3 = parseUtil.parseInteger(preparationId3Str);
+        Double preparationDose3 = parseUtil.parseDouble(preparationDose3Str);
+        
+        // Build model configuration
+        ModelConfiguration config = new ModelConfiguration();
+        config.setModelId("WLEAFBLTCH");
+        // Get weather data from weather station
+        PointOfInterestWeatherStation weatherStation = em.find(PointOfInterestWeatherStation.class, weatherStationId);
+        WeatherDataSourceUtil wsdUtil = new WeatherDataSourceUtil();
+        
+        String[] parameterList;
+        
+        // End date for weather data depends on season
+        // We try to add 5 months to the sowing date. If that's in the future,
+        // We add 10 days to today
+        Date dateOfFirstWeatherData;
+        Date dateOfLastWeatherData;
+        Calendar cal = Calendar.getInstance(timeZone);
+        cal.setTime(sowingDate);
+        // If winter wheat, the sowing date is after August 1st. We get weather 
+        // data from March 1st the following year. And we need soil temperature
+        if(cal.get(Calendar.MONTH) > Calendar.JULY)
+        {
+            cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) + 1);
+            cal.set(Calendar.MONTH, Calendar.MARCH);
+            cal.set(Calendar.DATE, 1);
+            parameterList = new String[]{
+                        WeatherElements.TEMPERATURE_MEAN,
+                        WeatherElements.PRECIPITATION,
+                        WeatherElements.SOIL_TEMPERATURE_10CM_MEAN
+                    };
+        }
+        else
+        {
+            cal.add(Calendar.MONTH, -1);
+            parameterList = new String[]{
+                        WeatherElements.TEMPERATURE_MEAN,
+                        WeatherElements.PRECIPITATION
+                    };
+        }
+        dateOfFirstWeatherData = cal.getTime();
+        cal.add(Calendar.MONTH, 6);
+        Date fiveMonthsAfterSowingDate = cal.getTime();
+        if(fiveMonthsAfterSowingDate.after(SystemTime.getSystemTime()))
+        {
+            cal.setTime(SystemTime.getSystemTime());
+            cal.add(Calendar.DATE, 10);
+            dateOfLastWeatherData = cal.getTime();
+        }
+        else
+        {
+            dateOfLastWeatherData = fiveMonthsAfterSowingDate;
+        }
+
+        List<WeatherObservation> observations;
+        try {
+             observations = wsdUtil.getWeatherObservations(
+                    weatherStation,
+                    WeatherObservation.LOG_INTERVAL_ID_1H,
+                    parameterList,
+                    dateOfFirstWeatherData, 
+                    dateOfLastWeatherData
+            );
+        } catch (WeatherDataSourceException ex) {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage()).build();
+        }
+        
+        if(observations == null || observations.isEmpty())
+        {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Could not find weather data for weather station with id=" + weatherStationId).build();
+        }
+        
+        // Mandatory parameters
+        config.setConfigParameter("wheatType", wheatType);
+        config.setConfigParameter("observations", observations);
+        config.setConfigParameter("timeZone", timeZone.getID());
+        config.setConfigParameter("sowingDate", sowingDateStr);
+        config.setConfigParameter("plowed", plowed);
+        config.setConfigParameter("previousSeasonCropType", sameCropAsLastSeason ? 1 : 2 );
+        // Optional parameters
+        if(observationDate != null && observationValue != null)
+        {
+            ObservationImpl observation = new ObservationImpl();
+            observation.setName("Septoria tritici");
+            observation.setObservationData("{\"percentInfectedLeaves\": " + observationValue + "}");
+            observation.setTimeOfObservation(observationDate);
+            config.setConfigParameter("observation", observation);
+        }
+        if(sprayingDate != null && preparationId1 != null && preparationDose1 != null)
+        {
+            SprayingImpl spraying = new SprayingImpl();
+            spraying.setSprayingDate(sprayingDate);
+            WheatLeafBlotchPreparationEffectFactor preparationEffectFactor1 = em.createNamedQuery("WheatLeafBlotchPreparationEffectFactor.findByPreparationId", WheatLeafBlotchPreparationEffectFactor.class)
+                    .setParameter("preparationId", preparationId1).getSingleResult();
+            WheatLeafBlotchPreparationEffectFactor preparationEffectFactor2 = null;
+            WheatLeafBlotchPreparationEffectFactor preparationEffectFactor3 = null;
+            Integer denominator = 1;
+            if(preparationId2 != null && preparationDose2 != null)
+            {
+                preparationEffectFactor2 = em.createNamedQuery("WheatLeafBlotchPreparationEffectFactor.findByPreparationId", WheatLeafBlotchPreparationEffectFactor.class)
+                    .setParameter("preparationId", preparationId2).getSingleResult();
+                denominator++;
+            }
+            if(preparationId3 != null && preparationDose3 != null)
+            {
+                preparationEffectFactor3 = em.createNamedQuery("WheatLeafBlotchPreparationEffectFactor.findByPreparationId", WheatLeafBlotchPreparationEffectFactor.class)
+                    .setParameter("preparationId", preparationId3).getSingleResult();
+                denominator++;
+            }
+            spraying.setSprayingEffectFactor(
+                    (1 - (preparationDose1 * preparationEffectFactor1.getFactorValue() / preparationEffectFactor1.getFullDosisMlDaa())
+                    + (preparationEffectFactor2 != null ? (1 - (preparationDose2 * preparationEffectFactor2.getFactorValue() / preparationEffectFactor2.getFullDosisMlDaa())) : 0.0 )
+                    + (preparationEffectFactor3 != null ? (1 - (preparationDose3 * preparationEffectFactor3.getFactorValue() / preparationEffectFactor3.getFullDosisMlDaa())) : 0.0)
+                    ) / denominator
+            );
+            //System.out.println("sprayingEffectFactor=" + spraying.getSprayingEffectFactor());
+            config.setConfigParameter("spraying", spraying);
+        }
+        
+        
+        // Must get the VIPSCore user id for this organization
+        Organization org = em.find(Organization.class, organizationId);
+        Integer VIPSCoreUserId = org.getDefaultVipsCoreUserId();
+        
+        List<Result> results;
+        try
+        {
+             results = SessionControllerGetter.getForecastBean().runForecast(config, VIPSCoreUserId);
+        }
+        catch(RunModelException ex)
+        {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ex.getMessage()).build();
+        }
+        return Response.ok().entity(results).build();
+    }
+    
+    
+}
diff --git a/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchPreparationEffectFactor.java b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchPreparationEffectFactor.java
new file mode 100644
index 00000000..d0e53006
--- /dev/null
+++ b/src/main/java/no/nibio/vips/logic/modules/wheatleafblotch/WheatLeafBlotchPreparationEffectFactor.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2015 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.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @copyright 2018 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+@Entity
+@Table(name = "wheatleafblotch_preparation_effect_factor", schema = "wheatleafb")
+@XmlRootElement
+@NamedQueries({
+    @NamedQuery(name = "WheatLeafBlotchPreparationEffectFactor.findAll", query = "SELECT p FROM WheatLeafBlotchPreparationEffectFactor p"),
+    @NamedQuery(name = "WheatLeafBlotchPreparationEffectFactor.findByPreparationId", query = "SELECT p FROM WheatLeafBlotchPreparationEffectFactor p WHERE p.preparationId = :preparationId"),
+    @NamedQuery(name = "WheatLeafBlotchPreparationEffectFactor.findByFullDosisMlDaa", query = "SELECT p FROM WheatLeafBlotchPreparationEffectFactor p WHERE p.fullDosisMlDaa = :fullDosisMlDaa"),
+    @NamedQuery(name = "WheatLeafBlotchPreparationEffectFactor.findByFactorValue", query = "SELECT p FROM WheatLeafBlotchPreparationEffectFactor p WHERE p.factorValue = :factorValue")})
+public class WheatLeafBlotchPreparationEffectFactor implements Serializable {
+    private static final long serialVersionUID = 1L;
+    @Id
+    @Basic(optional = false)
+    @NotNull
+    @Column(name = "preparation_id")
+    private Integer preparationId;
+    @Column(name = "full_dosis_ml_daa")
+    private Double fullDosisMlDaa;
+    @Column(name = "factor_value")
+    private Double factorValue;
+
+    public WheatLeafBlotchPreparationEffectFactor() {
+    }
+
+    public WheatLeafBlotchPreparationEffectFactor(Integer preparationId) {
+        this.preparationId = preparationId;
+    }
+
+    public Integer getPreparationId() {
+        return preparationId;
+    }
+
+    public void setPreparationId(Integer preparationId) {
+        this.preparationId = preparationId;
+    }
+
+    public Double getFullDosisMlDaa() {
+        return fullDosisMlDaa;
+    }
+
+    public void setFullDosisMlDaa(Double fullDosisMlDaa) {
+        this.fullDosisMlDaa = fullDosisMlDaa;
+    }
+
+    public Double getFactorValue() {
+        return factorValue;
+    }
+
+    public void setFactorValue(Double factorValue) {
+        this.factorValue = factorValue;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 0;
+        hash += (preparationId != null ? preparationId.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 WheatLeafBlotchPreparationEffectFactor)) {
+            return false;
+        }
+        WheatLeafBlotchPreparationEffectFactor other = (WheatLeafBlotchPreparationEffectFactor) object;
+        if ((this.preparationId == null && other.preparationId != null) || (this.preparationId != null && !this.preparationId.equals(other.preparationId))) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "no.nibio.vips.logic.modules.barleynetblotch.PreparationEffectFactor[ preparationId=" + preparationId + " ]";
+    }
+
+}
diff --git a/src/main/webapp/js/observationMap.js b/src/main/webapp/js/observationMap.js
index 5a132bb7..fa6cd64a 100755
--- a/src/main/webapp/js/observationMap.js
+++ b/src/main/webapp/js/observationMap.js
@@ -198,8 +198,8 @@ var initMap = function(
             var illustrationElm = "";
             if(observation.observationIllustrationSet.length == 1)
             {
-                    var illustration = observation.observationIllustrationSet[0]; 
-                    illustrationElm = "<img src='/static/images/observations/" + observation.organismId + "/" + illustration.observationIllustrationPK.fileName + "' class='img-responsive'/>";
+                var illustration = observation.observationIllustrationSet[0]; 
+                illustrationElm = "<img src='/static/images/observations/" + observation.organismId + "/" + illustration.observationIllustrationPK.fileName + "' class='img-responsive'/>";
             }
             // Create the popup, showing it
             poiDetails.popover({
-- 
GitLab