Commit 63795062 authored by Matija Obreza's avatar Matija Obreza

Working WorldClim example

parent 31d94144
......@@ -613,6 +613,7 @@
<include>syronex-colorpicker.min.js</include>
<include>jquery.flot.min.js</include>
<include>jquery.flot.pie.min.js</include>
<include>jquery.flot.fillbetween.min.js</include>
</includes>
</aggregation>
......
......@@ -46,6 +46,9 @@ public class Descriptor extends BusinessModel {
@Lob
@Type(type = "org.hibernate.type.TextType")
private String description;
@Column(length=20)
private String uom;
public String getTitle() {
return title;
......@@ -71,6 +74,14 @@ public class Descriptor extends BusinessModel {
this.description = description;
}
public String getUom() {
return uom;
}
public void setUom(String uom) {
this.uom = uom;
}
@Override
public String toString() {
return MessageFormat.format("Descriptor id={0,number,#} name={1}", id, code);
......
package org.genesys2.server.model.json;
import java.util.HashMap;
import java.util.Map;
import org.genesys2.server.model.impl.Descriptor;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* Helper class for temperature and precipitation charts
*/
public class WorldclimJson {
// Monthlies
private Integer[] precipitation = new Integer[12];
private Double[] tempMin = new Double[12];
private Double[] tempMean = new Double[12];
private Double[] tempMax = new Double[12];
@JsonIgnore
private Map<Descriptor, Object> other = new HashMap<Descriptor, Object>();
public Integer[] getPrecipitation() {
return precipitation;
}
public Double[] getTempMax() {
return tempMax;
}
public Double[] getTempMean() {
return tempMean;
}
public Double[] getTempMin() {
return tempMin;
}
public void addOther(Descriptor descriptor, Object val) {
other.put(descriptor, val);
}
public Map<Descriptor, Object> getOther() {
return this.other;
}
}
......@@ -33,4 +33,7 @@ public interface DatasetRowRepository extends JpaRepository<DatasetRow<?>, Long>
@Query("select dr from DatasetRow dr where dr.dataset=?1 and dr.descriptor=?2 and dr.accession.id in ( ?3 )")
List<DatasetRow<?>> list(Dataset dataset, Descriptor descriptor, Set<Long> ids);
@Query("select dr from DatasetRow dr where dr.dataset=?1 and dr.accession.id=?2")
List<DatasetRow<?>> list(Dataset dataset, long accessionId);
}
......@@ -24,6 +24,7 @@ import java.util.UUID;
import org.genesys2.server.model.impl.Dataset;
import org.genesys2.server.model.impl.DatasetRow;
import org.genesys2.server.model.impl.Descriptor;
import org.genesys2.server.model.json.WorldclimJson;
public interface Dataset2Service {
......@@ -41,5 +42,8 @@ public interface Dataset2Service {
void worldclimUpdate(Dataset dataset, Descriptor descriptor, Set<Long> ids, MappedByteBuffer buffer, short nullValue, double factor);
List<DatasetRow<?>> getForAccessionId(Dataset dataset, long accessionId);
WorldclimJson jsonForAccessionId(Dataset worldClimDataset, long accessionId);
}
......@@ -21,6 +21,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
......@@ -34,6 +36,7 @@ import org.genesys2.server.model.impl.DatasetRow;
import org.genesys2.server.model.impl.Descriptor;
import org.genesys2.server.model.impl.TraitDblValue;
import org.genesys2.server.model.impl.TraitIntValue;
import org.genesys2.server.model.json.WorldclimJson;
import org.genesys2.server.persistence.domain.AccessionGeoRepository;
import org.genesys2.server.persistence.domain.AccessionRepository;
import org.genesys2.server.persistence.domain.DatasetRepository;
......@@ -166,7 +169,7 @@ public class Dataset2ServiceImpl implements Dataset2Service {
if (LOG.isDebugEnabled()) {
LOG.debug("tile=" + tileIndex + " val=" + val + " lat=" + lat + " lon=" + lon + " aid=" + dr.getAccession().getId());
}
if (factor < 1.0) {
// FIXME Find a better way!
TraitDblValue tiv = (TraitDblValue) dr;
......@@ -196,4 +199,40 @@ public class Dataset2ServiceImpl implements Dataset2Service {
saveData(toSave);
LOG.info("Done saving.");
}
@Override
public List<DatasetRow<?>> getForAccessionId(Dataset dataset, long accessionId) {
return datasetRowRepository.list(dataset, accessionId);
}
@Override
public WorldclimJson jsonForAccessionId(Dataset dataset, long accessionId) {
WorldclimJson wc = new WorldclimJson();
List<DatasetRow<?>> data = getForAccessionId(dataset, accessionId);
for (DatasetRow<?> dr : data) {
String variable = dr.getDescriptor().getCode();
Matcher matcher = Pattern.compile("(prec|tmin|tmean|tmax)(\\d{1,2})").matcher(variable);
if (!matcher.matches()) {
// Add to "Other"
wc.addOther(dr.getDescriptor(), dr.getVal());
continue;
}
String varName = matcher.group(1);
int month = Integer.parseInt(matcher.group(2));
if ("prec".equals(varName)) {
wc.getPrecipitation()[month - 1] = (Integer) dr.getVal();
} else if ("tmin".equals(varName)) {
wc.getTempMin()[month - 1] = (Double) dr.getVal();
} else if ("tmean".equals(varName)) {
wc.getTempMean()[month - 1] = (Double) dr.getVal();
} else if ("tmax".equals(varName)) {
wc.getTempMax()[month - 1] = (Double) dr.getVal();
}
}
return wc;
}
}
......@@ -105,11 +105,11 @@ public class WorldClimUpdater implements InitializingBean {
public void downloadAll() {
try {
downloadAndExtract(worldClimDir, "alt_2-5m_bil.zip");
// downloadAndUpdate("prec_2-5m_bil.zip", 1.0f);
// downloadAndUpdate("tmin_2-5m_bil.zip", 0.1f);
// downloadAndUpdate("tmax_2-5m_bil.zip", 0.1f);
// downloadAndUpdate("tmean_2-5m_bil.zip", 0.1f);
// downloadAndUpdate("bio_2-5m_bil.zip", 1.0f);
downloadAndExtract(worldClimDir, "prec_2-5m_bil.zip");
downloadAndExtract(worldClimDir, "tmin_2-5m_bil.zip");
downloadAndExtract(worldClimDir, "tmax_2-5m_bil.zip");
downloadAndExtract(worldClimDir, "tmean_2-5m_bil.zip");
downloadAndExtract(worldClimDir, "bio_2-5m_bil.zip");
} catch (IOException e) {
LOG.error("Failed to download and import", e);
}
......@@ -219,75 +219,6 @@ public class WorldClimUpdater implements InitializingBean {
return ggf.getHeaderFile().exists() && ggf.getDataFile().exists();
}
// public void downloadAndUpdate(String file, float factor) throws
// IOException {
// File tempFile = download(file);
//
// // We have the tempFile ready.
// try {
// updateFromFile(tempFile, factor);
// } finally {
// tempFile.delete();
// }
//
// }
//
// private void updateFromFile(File tempFile, float factor) throws
// IOException {
//
// LOG.debug("Updating worldclim data from " + tempFile.getAbsolutePath());
// GenericGridZipFile ggzf = GenericGridZipFile.open(tempFile);
//
// try {
// for (String headerFileName : ggzf.getHeaderFileNames()) {
// LOG.debug("Found .hdr " + headerFileName);
//
// Header header = ggzf.readHeader(headerFileName);
// String variableName = header.getName();
//
// // Hackety-hack!
// if ("bio3".equals(variableName)) {
// factor = .01f;
// } else if ("bio4".equals(variableName)) {
// factor = .01f;
// }
//
// double xDim = header.getxDim();
// int precision = (int) (xDim / 0.008333333333333333d);
//
// LOG.info("Starting import of " + header.getName() + " var=" +
// header.getAttributes().get("Variable") + " precision=" + precision);
//
// updateWorldClim(precision, variableName, ggzf, header, factor);
// }
// } finally {
// ggzf.close();
// }
// }
// @Transactional
// public void updateWorldClim(int precision, String variableName,
// GenericGridZipFile ggzf, Header header, float factor) throws IOException
// {
// GenericGridDataReader ggdr = ggzf.getReader(header);
//
// worldClimService.clearData(precision, variableName);
//
// try {
// int offset = 0;
// for (int j = 0; j < header.getRows(); j++) {
// LOG.debug("Reading band " + j + " of " + header.getRows());
// Short[] band = ggdr.readBandData();
// LOG.debug("Retrieved band " + j + " of " + header.getRows());
// worldClimService.insertData(precision, variableName, band, factor,
// offset);
// offset += band.length;
// }
// } finally {
// IOUtils.closeQuietly(ggdr);
// }
// }
/**
* Download WorldClim file to a temporary file
*
......
......@@ -17,11 +17,14 @@
package org.genesys2.server.servlet.controller;
import java.util.List;
import java.util.UUID;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.genesys.Taxonomy2;
import org.genesys2.server.model.impl.Dataset;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.service.CropService;
import org.genesys2.server.service.Dataset2Service;
import org.genesys2.server.service.FilterConstants;
import org.genesys2.server.service.GenesysService;
import org.genesys2.server.service.InstituteService;
......@@ -40,6 +43,8 @@ import org.springframework.web.bind.annotation.RequestParam;
@RequestMapping("/acn")
public class AccessionController extends BaseController {
private static final UUID WORLDCLIM_DATASET_UUID = UUID.fromString("BC84433B-A626-4BDF-97D3-DB36D79499C6");
@Autowired
private InstituteService instituteService;
......@@ -54,6 +59,9 @@ public class AccessionController extends BaseController {
@Autowired
private CropService cropService;
@Autowired
private Dataset2Service dataset2Service;
@RequestMapping("/id/{accessionId}")
public String view(ModelMap model, @PathVariable(value = "accessionId") long accessionId) {
......@@ -78,6 +86,11 @@ public class AccessionController extends BaseController {
model.addAttribute("crops", cropService.getCrops(accession.getTaxonomy()));
// Worldclim data
Dataset worldClimDataset=dataset2Service.getDataSet(WORLDCLIM_DATASET_UUID);
model.addAttribute("worldclim", dataset2Service.getForAccessionId(worldClimDataset, accessionId));
model.addAttribute("worldclimJson", dataset2Service.jsonForAccessionId(worldClimDataset, accessionId));
return "/accession/details";
}
......
......@@ -16,6 +16,9 @@
package org.genesys2.server.servlet.controller;
import java.text.DateFormatSymbols;
import java.util.Locale;
import org.genesys2.server.exception.UserException;
import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.Crop;
......@@ -26,6 +29,9 @@ import org.genesys2.server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
public class JspHelper {
@Autowired
......@@ -34,6 +40,8 @@ public class JspHelper {
private GeoService geoService;
@Autowired
private CropService cropService;
@Autowired
private ObjectMapper objectMapper;
public String userFullName(Long userId) {
if (userId == null) {
......@@ -57,8 +65,22 @@ public class JspHelper {
public Country getCountry(String iso3) {
return geoService.getCountry(iso3);
}
public Crop getCrop(String shortName) {
return cropService.getCrop(shortName);
}
public String toJson(Object object) throws JsonProcessingException {
return objectMapper.writer().writeValueAsString(object);
}
public String[] monthNames(Locale locale) {
DateFormatSymbols dfs = new DateFormatSymbols(locale);
return dfs.getMonths();
}
public String[] monthShortNames(Locale locale) {
DateFormatSymbols dfs = new DateFormatSymbols(locale);
return dfs.getShortMonths();
}
}
......@@ -589,5 +589,15 @@ logger.appenders=Log appenders
menu.admin.loggers=Loggers
menu.admin.caches=Caches
worldclim.monthly.title=Climate at collecting site
worldclim.monthly.precipitation.title=Monthly precipitation
worldclim.monthly.temperatures.title=Monthly temperatures
worldclim.monthly.precipitation=Monthly precipitation [mm]
worldclim.monthly.tempMin=Minimum temperature [°C]
worldclim.monthly.tempMean=Mean temperature [°C]
worldclim.monthly.tempMax=Maximum temperature [°C]
worldclim.other-climate=Other climatic data
download.page.title=Before you download
download.download-now=Start download, I will wait.
......@@ -56,6 +56,7 @@
<script type="text/javascript" src="<c:url value="/html/js/syronex-colorpicker-min.js" />"></script>
<script type="text/javascript" src="<c:url value="/html/js/jquery.flot.min.js" />"></script>
<script type="text/javascript" src="<c:url value="/html/js/jquery.flot.pie.min.js" />"></script>
<script type="text/javascript" src="<c:url value="/html/js/jquery.flot.fillbetween.min.js" />"></script>
</c:when>
<c:when test="${requestContext.theme.name eq 'all'}">
<script type="text/javascript" src="<c:url value="/html/js/jquery.js" />"></script>
......@@ -67,7 +68,7 @@
<script type="text/javascript" src="<c:url value="/html/js/syronex-colorpicker.js" />"></script>
<script type="text/javascript" src="<c:url value="/html/js/jquery.flot.js" />"></script>
<script type="text/javascript" src="<c:url value="/html/js/jquery.flot.pie.js" />"></script>
<script type="text/javascript" src="<c:url value="/html/js/jquery.flot.fillbetween.js" />"></script>
</c:when>
<c:otherwise>
<script type="text/javascript" src="<c:url value="/html/js/jquery.js" />"></script>
......@@ -80,6 +81,7 @@
<script type="text/javascript" src="<c:url value="/html/js/syronex-colorpicker.js" />"></script>
<script type="text/javascript" src="<c:url value="/html/js/jquery.flot.js" />"></script>
<script type="text/javascript" src="<c:url value="/html/js/jquery.flot.pie.js" />"></script>
<script type="text/javascript" src="<c:url value="/html/js/jquery.flot.fillbetween.js" />"></script>
</c:otherwise>
</c:choose>
......
......@@ -310,6 +310,82 @@
</c:if>
</c:if>
<!-- WorldClim.org -->
<c:if test="${worldclim ne null and worldclim.size() gt 0}">
<h4><spring:message code="worldclim.monthly.title" /></h4>
<!-- Charts -->
<div class="row">
<div class="col-xs-12 col-sm-6">
<h5><spring:message code="worldclim.monthly.temperatures.title" /></h5>
<div id="temperatureChart" style="height:150px;"></div>
</div>
<div class="col-xs-12 col-sm-6">
<h5><spring:message code="worldclim.monthly.precipitation.title" /></h5>
<div id="precipitationChart" style="height:150px;"></div>
</div>
</div>
<!-- Data -->
<div class="row">
<div class="col-sm-offset-4 col-sm-8 col-xs-12">
<div class="row">
<c:forEach var="i" begin="0" end="11">
<div class="col-xs-1 text-right"><strong><c:out value="${jspHelper.monthShortNames(pageContext.response.locale)[i]}" /></strong></div>
</c:forEach>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4 col-xs-12"><spring:message code="worldclim.monthly.tempMin" /></div>
<div class="col-sm-8 col-xs-12">
<div class="row">
<c:forEach var="i" begin="0" end="11">
<div class="col-xs-1 text-right"><fmt:formatNumber value="${worldclimJson.tempMin[i]}" /></div>
</c:forEach>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4 col-xs-12"><spring:message code="worldclim.monthly.tempMean" /></div>
<div class="col-sm-8 col-xs-12">
<div class="row">
<c:forEach var="i" begin="0" end="11">
<div class="col-xs-1 text-right"><fmt:formatNumber value="${worldclimJson.tempMean[i]}" /></div>
</c:forEach>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4 col-xs-12"><spring:message code="worldclim.monthly.tempMax" /></div>
<div class="col-sm-8 col-xs-12">
<div class="row">
<c:forEach var="i" begin="0" end="11">
<div class="col-xs-1 text-right"><fmt:formatNumber value="${worldclimJson.tempMax[i]}" /></div>
</c:forEach>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4 col-xs-12"><spring:message code="worldclim.monthly.precipitation" /></div>
<div class="col-sm-8 col-xs-12">
<div class="row">
<c:forEach var="i" begin="0" end="11">
<div class="col-xs-1 text-right"><fmt:formatNumber value="${worldclimJson.precipitation[i]}" /></div>
</c:forEach>
</div>
</div>
</div>
<h4><spring:message code="worldclim.other-climate" /></h4>
<c:forEach items="${worldclimJson.other.keySet()}" var="worldclimDescriptor">
<div class="row">
<div class="col-xs-4"><c:out value="${worldclimDescriptor.title}" /></div>
<div class="col-xs-8"><fmt:formatNumber value="${worldclimJson.other[worldclimDescriptor]}" /><span class="uom">${worldclimDescriptor.uom}</span></div>
</div>
</c:forEach>
</c:if>
<c:if test="${svalbardData ne null}">
<h4><spring:message code="accession.svalbard-data" /></h4>
......@@ -411,7 +487,58 @@
});
</script>
</c:if>
<c:if test="${worldclim ne null and worldclim.size() gt 0}">
<script type="text/javascript">
var monthNames = ${jspHelper.toJson(jspHelper.monthShortNames(pageContext.response.locale))};
var worldclim = ${jspHelper.toJson(worldclimJson)};
function arrayToData(array) {
var ret=[];
for (var i=0; i<12; i++) {
ret[i]=[i, array[i]];
}
return ret;
}
var temperatureChart = [
{ label: "Temperatures", data: arrayToData(worldclim.tempMean), lines: { show: true }, color: "rgb(255,50,50)" },
{ id: "max", data: arrayToData(worldclim.tempMax), lines: { show: true, lineWidth: 0, fill: false }, color: "rgb(255,50,50)" },
{ id: "min", data: arrayToData(worldclim.tempMin), lines: { show: true, lineWidth: 0, fill: 0.2 }, color: "rgb(255,50,50)", fillBetween: "max" }
];
var precipitationChart = [
{ label: "Precipitation", data: arrayToData(worldclim.precipitation), bars: { show: true, align: "center" }, color: "rgb(50,50,255)" }
];
var temperaturePlot = $.plot($("#temperatureChart"), temperatureChart, {
xaxis: { tickLength: 0, tickFormatter: function(v, o) {
return monthNames[v];
} },
yaxis: {
tickFormatter: function (v) {
return v + " °C";
}
},
legend: { show: false }
});
var precipitationPlot = $.plot($("#precipitationChart"), precipitationChart, {
xaxis: { tickLength: 0, tickFormatter: function(v, o) {
return monthNames[v];
} },
yaxis: {
tickFormatter: function (v) {
return v + " mm";
}
},
legend: { show: false }
});
$( window ).resize(function() {
precipitationPlot.resize();
precipitationPlot.setupGrid();
precipitationPlot.draw();
temperaturePlot.resize();
temperaturePlot.setupGrid();
temperaturePlot.draw();
});
</script>
</c:if>
<script type="text/javascript">
<%@include file="/WEB-INF/jsp/wiews/ga.jsp"%>
_pageDim = { institute: '${accession.instituteCode}', genus: '${accession.taxonomy.genus}' };
......
/* Flot plugin for computing bottoms for filled line and bar charts.
Copyright (c) 2007-2014 IOLA and Ole Laursen.
Licensed under the MIT license.
The case: you've got two series that you want to fill the area between. In Flot
terms, you need to use one as the fill bottom of the other. You can specify the
bottom of each data point as the third coordinate manually, or you can use this
plugin to compute it for you.
In order to name the other series, you need to give it an id, like this:
var dataset = [
{ data: [ ... ], id: "foo" } , // use default bottom
{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
];
$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
As a convenience, if the id given is a number that doesn't appear as an id in
the series, it is interpreted as the index in the array instead (so fillBetween:
0 can also mean the first series).
Internally, the plugin modifies the datapoints in each series. For line series,
extra data points might be inserted through interpolation. Note that at points
where the bottom line is not defined (due to a null point or start/end of line),
the current line will show a gap too. The algorithm comes from the
jquery.flot.stack.js plugin, possibly some code could be shared.
*/
(function ( $ ) {
var options = {
series: {
fillBetween: null // or number
}
};
function init( plot ) {
function findBottomSeries( s, allseries ) {
var i;
for ( i = 0; i < allseries.length; ++i ) {
if ( allseries[ i ].id === s.fillBetween ) {
return allseries[ i ];
}
}
if ( typeof s.fillBetween === "number" ) {
if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) {
return null;
}
return allseries[ s.fillBetween ];
}
return null;
}
function computeFillBottoms( plot, s, datapoints ) {
if ( s.fillBetween == null ) {
return;
}
var other = findBottomSeries( s, plot.getData() );
if ( !other ) {