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

GrowthStageService v1 based on regional GS estimates

parent f362fba5
Branches
Tags
2 merge requests!22Develop,!18Spotit nordic map layers
......@@ -483,7 +483,96 @@ public class PointOfInterestBean {
.setParameter("name", dataSourceName)
.getSingleResult();
}
/**
* Utility method to find the nearest weather station
* @param location
* @return
*/
public PointOfInterestWeatherStation findClosestWeatherStation(com.vividsolutions.jts.geom.Coordinate location)
{
return this.findClosestWeatherStation(location.x, location.y);
}
public PointOfInterestWeatherStation findClosestWeatherStation(Double longitude, Double latitude)
{
List<PointOfInterestWeatherStation> weatherStations = em.createNamedQuery("PointOfInterestWeatherStation.findAll", PointOfInterestWeatherStation.class)
.getResultList();
return this.findClosestWeatherStation(longitude, latitude, weatherStations);
}
/**
* Utility method to find the nearest weather station
* @param location
* @return
*/
public PointOfInterestWeatherStation findClosestWeatherStation(com.vividsolutions.jts.geom.Coordinate location, List<PointOfInterestWeatherStation> weatherStations)
{
return this.findClosestWeatherStation(location.x, location.y, weatherStations);
}
/**
* Utility method to find the nearest weather station
* @param longitude
* @param latitude
* @return
*/
public PointOfInterestWeatherStation findClosestWeatherStation(Double longitude, Double latitude, List<PointOfInterestWeatherStation> weatherStations)
{
PointOfInterestWeatherStation retVal = null;
Double minimumDistance = null; // km
for(PointOfInterestWeatherStation ws:weatherStations)
{
Double distance = this.calculateDistance(latitude, longitude, ws.getLatitude(), ws.getLongitude());
if(minimumDistance == null || minimumDistance >= distance)
{
minimumDistance = distance;
retVal = ws;
}
}
return retVal;
}
/**
* Based on <a href="http://www.movable-type.co.uk/scripts/latlong.html">this</a>. Explanation:
* <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 WGS84 latitude of point 1
* @param long1 WGS84 longitude of point 1
* @param lat2 WGS84 latitude of point 2
* @param long2 WGS84 longitude of point 2
* @return distance in km
*/
public Double calculateDistance(Double lat1, Double long1, Double lat2, Double long2)
{
Integer R = 6371; // km
Double dLat = Math.toRadians(lat2-lat1);
Double dLon = Math.toRadians(long2-long1);
lat1 = Math.toRadians(lat1);
lat2 = Math.toRadians(lat2);
Double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
Double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
Double d = R * c;
return d;
}
}
/*
* Copyright (c) 2020 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;
/**
* @copyright 2020 <a href="http://www.nibio.no/">NIBIO</a>
* @author Tor-Einar Skog <tor-einar.skog@nibio.no>
*/
@Entity
@Table(name = "growth_stage_location_date", schema = "wheatleafb")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "GrowthStageLocationDate.findAll", query = "SELECT g FROM GrowthStageLocationDate g"),
@NamedQuery(name = "GrowthStageLocationDate.findByPointOfInterestId", query = "SELECT g FROM GrowthStageLocationDate g WHERE g.growthStageLocationDatePK.pointOfInterestId = :pointOfInterestId"),
@NamedQuery(name = "GrowthStageLocationDate.findByCropOrganismId", query = "SELECT g FROM GrowthStageLocationDate g WHERE g.growthStageLocationDatePK.cropOrganismId = :cropOrganismId"),
@NamedQuery(name = "GrowthStageLocationDate.findByCropOrganismIdAndPointOfInterestId", query = "SELECT g FROM GrowthStageLocationDate g WHERE g.growthStageLocationDatePK.cropOrganismId = :cropOrganismId AND g.growthStageLocationDatePK.pointOfInterestId = :pointOfInterestId")
})
public class GrowthStageLocationDate implements Serializable, Comparable {
private static final long serialVersionUID = 1L;
@EmbeddedId
private GrowthStageLocationDatePK growthStageLocationDatePK;
@Column(name = "day_number")
private Integer dayNumber;
public GrowthStageLocationDate() {
}
public GrowthStageLocationDate(GrowthStageLocationDatePK growthStateLocationPK) {
this.growthStageLocationDatePK = growthStateLocationPK;
}
@Override
public int hashCode() {
int hash = 0;
hash += (getGrowthStageLocationDatePK() != null ? getGrowthStageLocationDatePK().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 GrowthStageLocationDate)) {
return false;
}
GrowthStageLocationDate other = (GrowthStageLocationDate) object;
if ((this.getGrowthStageLocationDatePK() == null && other.getGrowthStageLocationDatePK() != null) || (this.getGrowthStageLocationDatePK() != null && !this.growthStageLocationDatePK.equals(other.growthStageLocationDatePK))) {
return false;
}
return true;
}
@Override
public String toString() {
return "no.nibio.vips.logic.modules.wheatleafblotch.GrowthStageLocationDate[ growthStageLocationDatePK=" + getGrowthStageLocationDatePK() + " ]";
}
/**
* @return the dayNumber
*/
public Integer getDayNumber() {
return dayNumber;
}
/**
* @param dayNumber the dayNumber to set
*/
public void setDayNumber(Integer dayNumber) {
this.dayNumber = dayNumber;
}
@Override
public int compareTo(Object t) {
return this.getGrowthStageLocationDatePK().getGrowthStage().compareTo(((GrowthStageLocationDate) t).getGrowthStageLocationDatePK().getGrowthStage()
);
}
/**
* @return the growthStageLocationDatePK
*/
public GrowthStageLocationDatePK getGrowthStageLocationDatePK() {
return growthStageLocationDatePK;
}
/**
* @param growthStageLocationDatePK the growthStageLocationDatePK to set
*/
public void setGrowthStageLocationDatePK(GrowthStageLocationDatePK growthStageLocationDatePK) {
this.growthStageLocationDatePK = growthStageLocationDatePK;
}
}
/*
* Copyright (c) 2020 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.Organism;
import no.nibio.vips.logic.entity.PointOfInterest;
/**
* @copyright 2020 <a href="http://www.nibio.no/">NIBIO</a>
* @author Tor-Einar Skog <tor-einar.skog@nibio.no>
*/
@Embeddable
public class GrowthStageLocationDatePK implements Serializable {
@Basic(optional = false)
@NotNull
@JoinColumn(name = "crop_organism_id", referencedColumnName = "organism_id")
@ManyToOne
private Organism cropOrganismId;
@Basic(optional = false)
@NotNull
@JoinColumn(name = "point_of_interest_id", referencedColumnName = "point_of_interest_id")
@ManyToOne
private PointOfInterest pointOfInterestId;
@Column(name = "growth_stage")
private Integer growthStage;
public GrowthStageLocationDatePK() {
}
public GrowthStageLocationDatePK(Organism organism, PointOfInterest pointOfInterestId) {
this.cropOrganismId = organism;
this.pointOfInterestId = pointOfInterestId;
}
public Organism getCropOrganismId() {
return cropOrganismId;
}
public void setCropOrganismId(Organism cropOrganismId) {
this.cropOrganismId = cropOrganismId;
}
public PointOfInterest getPointOfInterestId() {
return pointOfInterestId;
}
public void setPointOfInterestId(PointOfInterest pointOfInterestId) {
this.pointOfInterestId = pointOfInterestId;
}
public Integer getGrowthStage() {
return growthStage;
}
public void setGrowthStage(Integer growthStage) {
this.growthStage = growthStage;
}
@Override
public int hashCode() {
int hash = 0;
hash += (int) cropOrganismId.getOrganismId();
hash += (int) pointOfInterestId.getPointOfInterestId();
hash += (int) growthStage;
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 GrowthStageLocationDatePK)) {
return false;
}
GrowthStageLocationDatePK other = (GrowthStageLocationDatePK) object;
if (this.cropOrganismId != other.cropOrganismId) {
return false;
}
if (this.pointOfInterestId != other.pointOfInterestId) {
return false;
}
if(! this.growthStage.equals(other.growthStage)){
return false;
}
return true;
}
@Override
public String toString() {
return "no.nibio.vips.logic.modules.wheatleafblotch.YieldLossPK[ organism=" + cropOrganismId + ", pointOfInterestId=" + pointOfInterestId + " ]";
}
}
/*
* Copyright (c) 2019 NIBIO <http://www.nibio.no/>.
* Copyright (c) 2020 NIBIO <http://www.nibio.no/>.
*
* This file is part of VIPSLogic.
* VIPSLogic is free software: you can redistribute it and/or modify
......@@ -19,20 +19,19 @@
package no.nibio.vips.logic.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.text.SimpleDateFormat;
import com.vividsolutions.jts.geom.Coordinate;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
......@@ -41,16 +40,21 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.annotations.GZIP;
import no.nibio.vips.logic.entity.Organism;
import no.nibio.vips.logic.entity.PointOfInterestWeatherStation;
import no.nibio.vips.logic.modules.wheatleafblotch.GrowthStageLocationDate;
import no.nibio.vips.logic.util.SessionControllerGetter;
/**
* @copyright 2019 <a href="http://www.nibio.no/">NIBIO</a>
* @copyright 2020 <a href="http://www.nibio.no/">NIBIO</a>
* @author Tor-Einar Skog <tor-einar.skog@nibio.no>
*/
@Path("rest/gs")
public class GrowthStageService {
@Context
private HttpServletRequest httpServletRequest;
@PersistenceContext(unitName="VIPSLogic-PU")
EntityManager em;
@GET
......@@ -63,30 +67,18 @@ public class GrowthStageService {
@QueryParam("season") Integer season
)
{
List<GrowthStageLocationDate> growthStageDatesForLocationAndCrop = this.getGrowthStageDatesForLocationAndCrop(location, organismId);
//SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
//format.setTimeZone(TimeZone.getTimeZone("Europe/Oslo")); // TODO get from param or default is UTC
List<Map<Integer,LocalDate>> retVal = new ArrayList<>();
List<Integer> growthStages = Arrays.stream(growthStagesStr.split(",")).map(str->Integer.valueOf(str)).collect(Collectors.toList());
for(Integer growthStage: growthStages)
{
growthStages.stream().map((growthStage) -> {
Map<Integer, LocalDate> gsDate = new HashMap<>();
if(growthStage.equals(32))
{
gsDate.put(growthStage, LocalDate.of(season != null ? season : LocalDate.now().getYear(), Month.JUNE,10));
}
else if(growthStage.equals(71))
{
gsDate.put(growthStage, LocalDate.of(season != null ? season : LocalDate.now().getYear(), Month.JULY,25));
}
else
{
gsDate.put(growthStage, LocalDate.of(season != null ? season : LocalDate.now().getYear(), Month.JANUARY,1));
}
gsDate.put(growthStage, this.getDateForGrowthStage(growthStageDatesForLocationAndCrop, growthStage, season));
return gsDate;
}).forEachOrdered((gsDate) -> {
retVal.add(gsDate);
}
});
return Response.ok().entity(retVal).build();
}
......@@ -99,17 +91,117 @@ public class GrowthStageService {
@QueryParam("location") String location
)
{
Integer retVal;
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
LocalDate date = LocalDate.parse(dateStr, formatter);
if(date.isBefore(LocalDate.of(LocalDate.now().getYear(), Month.JULY,1)))
List<GrowthStageLocationDate> growthStageDatesForLocationAndCrop = this.getGrowthStageDatesForLocationAndCrop(location, organismId);
//Integer[] retVal = {this.getGrowthStageForDate(growthStageDatesForLocationAndCrop, date)};
List<Map<LocalDate,Integer>> retVal = new ArrayList<>();
Map<LocalDate,Integer> actualRetVal = new HashMap<>();
actualRetVal.put(date, this.getGrowthStageForDate(growthStageDatesForLocationAndCrop, date));
retVal.add(actualRetVal);
return Response.ok().entity(retVal).build();
}
private Coordinate getLocationCoordinate(String location)
{
String[] lonLat = location.split(",");
Double longitude = Double.valueOf(lonLat[0]);
Double latitude = Double.valueOf(lonLat[1]);
Coordinate retVal = new Coordinate(longitude, latitude);
return retVal;
}
private Integer getGrowthStageForDate(List<GrowthStageLocationDate> datesForGrowthStages, LocalDate date)
{
Integer dateDayNum = date.getDayOfYear();
Collections.sort(datesForGrowthStages);
for(int i=0;i<datesForGrowthStages.size();i++)
{
retVal = 32;
Integer gsDayNum1 = datesForGrowthStages.get(i).getDayNumber();
// Case 1: Date is before the earliest registered GSDate in DB
// Case 2: Date is after the latest registered GSDate in DB
if((i==0 && dateDayNum < gsDayNum1 ) || (i == datesForGrowthStages.size()-1 && dateDayNum > gsDayNum1))
{
return null;
}
// Case 3: date equals the current list item GSDate: Return as is
if(dateDayNum.equals(gsDayNum1))
{
return datesForGrowthStages.get(i).getGrowthStageLocationDatePK().getGrowthStage();
}
Integer gsDayNum2 = datesForGrowthStages.get(i+1).getDayNumber();
// Case 4 date is between current and next GSDate
if(dateDayNum > gsDayNum1 && dateDayNum < gsDayNum2)
{
// We do linear interpolation to find the gs
Float deltaGS = (float) (datesForGrowthStages.get(i+1).getGrowthStageLocationDatePK().getGrowthStage() - datesForGrowthStages.get(i).getGrowthStageLocationDatePK().getGrowthStage())
/ (gsDayNum2 - gsDayNum1);
// How many days per gs between the two items?
//Float deltaDay = (float) (datesForGrowthStages.get(i+1).getDayNumber() - datesForGrowthStages.get(i).getDayNumber()) / (gs2-gs1);
Integer addedGS = Math.round((dateDayNum - gsDayNum1) * deltaGS);
return datesForGrowthStages.get(i).getGrowthStageLocationDatePK().getGrowthStage() + addedGS;
}
// How many gs per day between the two items?
//Double deltaGS = (double) (gs2-gs1) / (datesForGrowthStages.get(i+1).getDayNumber() - datesForGrowthStages.get(i).getDayNumber());
}
else
return null;
}
private LocalDate getDateForGrowthStage(List<GrowthStageLocationDate> datesForGrowthStages, Integer growthStage, Integer season)
{
Collections.sort(datesForGrowthStages);
for(int i=0;i<datesForGrowthStages.size();i++)
{
retVal = 71;
Integer gs1 = datesForGrowthStages.get(i).getGrowthStageLocationDatePK().getGrowthStage();
// Case 1: GS is lower than the lowest registered stage in DB
// Case 2: GS is higher than the highest registered stage in DB
if((i==0 && growthStage < gs1) || (i == datesForGrowthStages.size()-1 && growthStage > gs1))
{
return null;
}
// Case 3: GS equals the current list item GS: Return as is
if(growthStage.equals(gs1))
{
return LocalDate.ofYearDay(season, datesForGrowthStages.get(i).getDayNumber());
}
Integer gs2 = datesForGrowthStages.get(i+1).getGrowthStageLocationDatePK().getGrowthStage();
// Case 4 GS is between current and next GS
if(growthStage > gs1 && growthStage < gs2)
{
// We do linear interpolation to find the date
// How many days per gs between the two items?
Float deltaDay = (float) (datesForGrowthStages.get(i+1).getDayNumber() - datesForGrowthStages.get(i).getDayNumber()) / (gs2-gs1);
Integer addedDays = Math.round((growthStage - gs1) * deltaDay);
return LocalDate.ofYearDay(season, datesForGrowthStages.get(i).getDayNumber() + addedDays);
}
// How many gs per day between the two items?
//Double deltaGS = (double) (gs2-gs1) / (datesForGrowthStages.get(i+1).getDayNumber() - datesForGrowthStages.get(i).getDayNumber());
}
return Response.ok().entity(retVal).build();
return null;
}
private List<GrowthStageLocationDate> getGrowthStageDatesForLocationAndCrop(String location, Integer organismId)
{
List<PointOfInterestWeatherStation> stationsWithGrowthStageInfoForCrop = em.createNativeQuery(
"SELECT poi.*, poiws.* FROM public.point_of_interest_weather_station poiws, public.point_of_interest poi "
+ "WHERE poi.point_of_interest_id IN("
+ " SELECT point_of_interest_id FROM wheatleafb.growth_stage_location_date "
+ " WHERE crop_organism_id = :cropOrganismId "
+ ")"
+ "AND poiws.point_of_interest_id = poi.point_of_interest_id",
PointOfInterestWeatherStation.class)
.setParameter("cropOrganismId", organismId)
.getResultList();
PointOfInterestWeatherStation closestStation = SessionControllerGetter.getPointOfInterestBean().findClosestWeatherStation(this.getLocationCoordinate(location), stationsWithGrowthStageInfoForCrop);
return em.createNamedQuery("GrowthStageLocationDate.findByCropOrganismIdAndPointOfInterestId")
.setParameter("cropOrganismId", em.find(Organism.class, organismId))
.setParameter("pointOfInterestId", closestStation)
.getResultList();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment