package uk.ac.ox.zoo.seeg.abraid.mp.common.service.workflow.support;
import org.geotools.coverage.grid.GridCoverage2D;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.kubek2k.springockito.annotations.ReplaceWithMock;
import org.kubek2k.springockito.annotations.SpringockitoContextLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.*;
import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.DiseaseService;
import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.ModelRunService;
import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.ValidationParameterCacheService;
import uk.ac.ox.zoo.seeg.abraid.mp.common.util.GeometryUtils;
import uk.ac.ox.zoo.seeg.abraid.mp.common.util.raster.RasterUtils;
import uk.ac.ox.zoo.seeg.abraid.mp.common.web.RasterFilePathFactory;
import uk.ac.ox.zoo.seeg.abraid.mp.testutils.AbstractSpringIntegrationTests;
import java.io.File;
import java.io.IOException;
import static com.googlecode.catchexception.CatchException.catchException;
import static com.googlecode.catchexception.CatchException.caughtException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.offset;
import static org.mockito.Mockito.*;
/**
* Integration tests for the EnvironmentalSuitabilityHelper class.
* Copyright (c) 2014 University of Oxford
*/
@ContextConfiguration(loader = SpringockitoContextLoader.class, locations = {
"classpath:uk/ac/ox/zoo/seeg/abraid/mp/testutils/test-context.xml",
"classpath:uk/ac/ox/zoo/seeg/abraid/mp/common/config/beans.xml"
})
public class EnvironmentalSuitabilityHelperIntegrationTest extends AbstractSpringIntegrationTests {
// Parameters taken from the test raster files
private static final String LARGE_RASTER_FILENAME =
"Common/test/uk/ac/ox/zoo/seeg/abraid/mp/common/service/workflow/support/testdata/test_raster_large_double.tif";
private static final String ADMIN_RASTER_FILENAME =
"Common/test/uk/ac/ox/zoo/seeg/abraid/mp/common/service/workflow/support/testdata/admin_raster_large_double.tif";
private static final double LARGE_RASTER_COLUMNS = 720;
private static final double LARGE_RASTER_ROWS = 240;
private static final double LARGE_RASTER_XLLCORNER = -180;
private static final double LARGE_RASTER_YLLCORNER = -60;
private static final double LARGE_RASTER_CELLSIZE = 0.5;
private DiseaseGroup diseaseGroup;
@Autowired
private EnvironmentalSuitabilityHelper helper;
@Autowired
private DiseaseService diseaseService;
@Autowired
private ModelRunService modelRunService;
@Autowired
@ReplaceWithMock
private RasterFilePathFactory rasterFilePathFactory;
@Autowired
@ReplaceWithMock
private ValidationParameterCacheService cacheService;
@Before
public void setUp() {
diseaseGroup = diseaseService.getDiseaseGroupById(87);
reset(cacheService);
when(cacheService.getEnvironmentalSuitabilityFromCache(anyInt(), anyInt())).thenReturn(null);
}
@Test
public void getLatestMeanPredictionRasterReturnsNullIfNoRelevantModelRunsForThisDiseaseGroup() throws Exception {
// Arrange - 2 irrelevant model runs
ModelRun modelRun1 = createAndSaveModelRun("failed", diseaseGroup.getId(), ModelRunStatus.FAILED);
mockGetRasterFileForModelRun(modelRun1);
ModelRun modelRun2 = createAndSaveModelRun("different disease group", 1, ModelRunStatus.COMPLETED);
mockGetRasterFileForModelRun(modelRun2);
// Act
GridCoverage2D meanPredictionRaster = helper.getLatestMeanPredictionRaster(diseaseGroup);
// Assert
assertThat(meanPredictionRaster).isNull();
}
@Test
public void findEnvironmentalSuitabilityLowerLeftCorner() throws Exception {
findEnvironmentalSuitabilityPoint(LARGE_RASTER_XLLCORNER, LARGE_RASTER_YLLCORNER, 0.89);
findEnvironmentalSuitabilityPrecise(LARGE_RASTER_XLLCORNER, LARGE_RASTER_YLLCORNER, 0.89);
}
@Test
public void findEnvironmentalSuitabilityUpperRightCorner() throws Exception {
double upperRightCornerX = LARGE_RASTER_XLLCORNER + (LARGE_RASTER_COLUMNS - 1) * LARGE_RASTER_CELLSIZE;
double upperRightCornerY = LARGE_RASTER_YLLCORNER + (LARGE_RASTER_ROWS - 1) * LARGE_RASTER_CELLSIZE;
findEnvironmentalSuitabilityPoint(upperRightCornerX, upperRightCornerY, 0.79);
findEnvironmentalSuitabilityPrecise(upperRightCornerX, upperRightCornerY, 0.79);
}
@Test
public void findEnvironmentalSuitabilityInterpolated() throws Exception {
double lowerLeftCornerSlightlyShiftedX = LARGE_RASTER_XLLCORNER + (LARGE_RASTER_CELLSIZE * 0.5);
double lowerLeftCornerSlightlyShiftedY = LARGE_RASTER_YLLCORNER + (LARGE_RASTER_CELLSIZE * 0.5);
findEnvironmentalSuitabilityPoint(lowerLeftCornerSlightlyShiftedX, lowerLeftCornerSlightlyShiftedY, 0.89);
findEnvironmentalSuitabilityPrecise(lowerLeftCornerSlightlyShiftedX, lowerLeftCornerSlightlyShiftedY, 0.89);
}
@Test
public void findEnvironmentalSuitabilityOutOfRasterRange() throws Exception {
double oneCellBeyondUpperRightCornerX = LARGE_RASTER_XLLCORNER + LARGE_RASTER_COLUMNS * LARGE_RASTER_CELLSIZE;
double oneCellBeyondUpperRightCornerY = LARGE_RASTER_YLLCORNER + LARGE_RASTER_ROWS * LARGE_RASTER_CELLSIZE;
findEnvironmentalSuitabilityPoint(oneCellBeyondUpperRightCornerX, oneCellBeyondUpperRightCornerY, null);
findEnvironmentalSuitabilityPrecise(oneCellBeyondUpperRightCornerX, oneCellBeyondUpperRightCornerY, null);
}
@Test
public void findEnvironmentalSuitabilityNoDataValueWithinRasterRange() throws Exception {
// The NODATA value in the raster is in column 6 row 12 (from the top left)
double noDataValueX = LARGE_RASTER_XLLCORNER + 5 * LARGE_RASTER_CELLSIZE;
double noDataValueY = LARGE_RASTER_YLLCORNER + (LARGE_RASTER_ROWS - 12) * LARGE_RASTER_CELLSIZE;
findEnvironmentalSuitabilityPoint(noDataValueX, noDataValueY, null);
findEnvironmentalSuitabilityPrecise(noDataValueX, noDataValueY, null);
}
@Test
public void findEnvironmentalSuitabilityNoDataInShape() throws Exception {
// Falls back to precise
double upperRightCornerX = LARGE_RASTER_XLLCORNER + (LARGE_RASTER_COLUMNS - 1) * LARGE_RASTER_CELLSIZE;
double upperRightCornerY = LARGE_RASTER_YLLCORNER + (LARGE_RASTER_ROWS - 1) * LARGE_RASTER_CELLSIZE;
findEnvironmentalSuitabilityShape(upperRightCornerX, upperRightCornerY, 987, LocationPrecision.COUNTRY, 0.79);
}
@Test
public void findEnvironmentalSuitabilityShapeHalfNoData() throws Exception {
findEnvironmentalSuitabilityShape(0, 0, 654, LocationPrecision.ADMIN1, 0.504699);
}
@Test
public void findEnvironmentalSuitabilityShapeFullPopulated() throws Exception {
findEnvironmentalSuitabilityShape(0, 0, 321, LocationPrecision.ADMIN2, 0.491874);
}
@Test
public void findEnvironmentalSuitabilityUsesCachedValue() throws Exception {
// Arrange
DiseaseOccurrence occurrence = createOccurrence(1, 1, 1, LocationPrecision.PRECISE);
ModelRun modelRun = createAndSaveModelRun("test name", diseaseGroup.getId(), ModelRunStatus.COMPLETED);
mockGetRasterFileForModelRun(modelRun);
GridCoverage2D suitabilityRaster = null;
GridCoverage2D[] adminRasters = null;
try {
suitabilityRaster = helper.getLatestMeanPredictionRaster(diseaseGroup);
adminRasters = helper.getSingleAdminRaster(LocationPrecision.PRECISE);
when(cacheService.getEnvironmentalSuitabilityFromCache(occurrence.getDiseaseGroup().getId(), occurrence.getLocation().getId())).thenReturn(12345d);
// Act
Double suitability = helper.findEnvironmentalSuitability(occurrence, suitabilityRaster, adminRasters);
// Assert
assertThat(suitability).isEqualTo(12345d);
} finally {
RasterUtils.disposeRaster(suitabilityRaster);
RasterUtils.disposeRasters(adminRasters);
}
}
@Test
public void createCroppedEnvironmentalSuitabilityRasterCropsCorrectArea() throws Exception {
// Arrange
File suitabilityRaster = new File(LARGE_RASTER_FILENAME);
File adminRaster = new File(ADMIN_RASTER_FILENAME);
int gaul = 321;
// Act
File cropped = helper.createCroppedEnvironmentalSuitabilityRaster(gaul, adminRaster, suitabilityRaster);
// Assert
File adminRasterCroppedBy321 = new File("Common/test/uk/ac/ox/zoo/seeg/abraid/mp/common/service/workflow/support/testdata/test_raster_large_double_cropped_by_321.tif");
assertThat(cropped).hasContentEqualTo(adminRasterCroppedBy321);
assertThat(cropped.delete()).isTrue();
}
@Test
public void createCroppedEnvironmentalSuitabilityRasterCropsCorrectAreaWithWaterBody() throws Exception {
// Arrange
File suitabilityRaster = new File(LARGE_RASTER_FILENAME);
File adminRaster = new File(ADMIN_RASTER_FILENAME);
int gaul = 654;
// Act
File cropped = helper.createCroppedEnvironmentalSuitabilityRaster(gaul, adminRaster, suitabilityRaster);
// Assert
File adminRasterCroppedBy654 = new File("Common/test/uk/ac/ox/zoo/seeg/abraid/mp/common/service/workflow/support/testdata/test_raster_large_double_cropped_by_654.tif");
assertThat(cropped).hasContentEqualTo(adminRasterCroppedBy654);
assertThat(cropped.delete()).isTrue();
}
@Test
public void createCroppedEnvironmentalSuitabilityRasterWhenNoMatchingPixels() throws Exception {
// Arrange
File suitabilityRaster = new File(LARGE_RASTER_FILENAME);
File adminRaster = new File(ADMIN_RASTER_FILENAME);
int gaul = 123;
// Act
catchException(helper).createCroppedEnvironmentalSuitabilityRaster(gaul, adminRaster, suitabilityRaster);
// Assert
assertThat(caughtException()).isInstanceOf(IOException.class);
assertThat(caughtException().getMessage()).isEqualTo("The specified country does not appear to cover any raster pixels.");
}
private ModelRun createAndSaveModelRun(String name, int diseaseGroupId, ModelRunStatus status) {
ModelRun modelRun = new ModelRun(name, diseaseService.getDiseaseGroupById(diseaseGroupId), "host", DateTime.now(), DateTime.now(), DateTime.now());
modelRun.setStatus(status);
modelRun.setResponseDate(DateTime.now());
modelRunService.saveModelRun(modelRun);
return modelRun;
}
private void mockGetRasterFileForModelRun(ModelRun modelRun) {
when(rasterFilePathFactory.getFullMeanPredictionRasterFile(same(modelRun)))
.thenReturn(new File(LARGE_RASTER_FILENAME));
}
private void mockGetAdminRasterFileForLevel(int level) {
when(rasterFilePathFactory.getAdminRaster(level)).thenReturn(new File(ADMIN_RASTER_FILENAME));
}
private void findEnvironmentalSuitabilityPrecise(double x, double y,
Double expectedEnvironmentalSuitability) throws Exception {
// Arrange
DiseaseOccurrence occurrence = createOccurrence(x, y, 1, LocationPrecision.PRECISE);
ModelRun modelRun = createAndSaveModelRun("test name", diseaseGroup.getId(), ModelRunStatus.COMPLETED);
mockGetRasterFileForModelRun(modelRun);
GridCoverage2D suitabilityRaster = null;
GridCoverage2D[] adminRasters = null;
try {
suitabilityRaster = helper.getLatestMeanPredictionRaster(diseaseGroup);
adminRasters = helper.getSingleAdminRaster(LocationPrecision.PRECISE);
// Act
Double suitability = helper.findEnvironmentalSuitability(occurrence, suitabilityRaster, adminRasters);
// Assert
assertThat(suitabilityRaster).isNotNull();
assertThat(adminRasters).isNotNull();
assertThat(adminRasters[0]).isNull();
assertThat(adminRasters[1]).isNull();
assertThat(adminRasters[2]).isNull();
if (expectedEnvironmentalSuitability != null) {
assertThat(suitability).isEqualTo(expectedEnvironmentalSuitability, offset(0.0000005));
verify(cacheService).saveEnvironmentalSuitabilityCacheEntry(occurrence.getDiseaseGroup().getId(), occurrence.getLocation().getId(), suitability);
} else {
assertThat(suitability).isNull();
}
} finally {
RasterUtils.disposeRaster(suitabilityRaster);
RasterUtils.disposeRasters(adminRasters);
}
}
private void findEnvironmentalSuitabilityPoint(double x, double y,
Double expectedEnvironmentalSuitability) throws Exception {
// Arrange
ModelRun modelRun = createAndSaveModelRun("test name 1", diseaseGroup.getId(), ModelRunStatus.COMPLETED);
mockGetRasterFileForModelRun(modelRun);
File suitabilityRaster = rasterFilePathFactory.getFullMeanPredictionRasterFile(modelRun);
double offsetForRounding = 0.00005;
// Act
Double suitability = helper.findPointEnvironmentalSuitability(suitabilityRaster, GeometryUtils.createPoint(x + offsetForRounding, y + offsetForRounding));
// Assert
if (expectedEnvironmentalSuitability != null) {
assertThat(suitability).isEqualTo(expectedEnvironmentalSuitability, offset(0.0000005));
} else {
assertThat(suitability).isNull();
}
}
private void findEnvironmentalSuitabilityShape(double x, double y, int gaul, LocationPrecision precision,
Double expectedEnvironmentalSuitability) throws Exception {
// Arrange
DiseaseOccurrence occurrence = createOccurrence(x, y, gaul, precision);
ModelRun modelRun = createAndSaveModelRun("test name", diseaseGroup.getId(), ModelRunStatus.COMPLETED);
mockGetRasterFileForModelRun(modelRun);
mockGetAdminRasterFileForLevel(precision.getModelValue());
mockGetRasterFileForModelRun(modelRun);
GridCoverage2D suitabilityRaster = null;
GridCoverage2D[] adminRasters = null;
try {
suitabilityRaster = helper.getLatestMeanPredictionRaster(diseaseGroup);
adminRasters = helper.getSingleAdminRaster(precision);
// Act
Double suitability = helper.findEnvironmentalSuitability(occurrence, suitabilityRaster, adminRasters);
// Assert
assertThat(suitabilityRaster).isNotNull();
assertThat(adminRasters).isNotNull();
assertThat(adminRasters[precision.getModelValue()]).isNotNull();
if (expectedEnvironmentalSuitability != null) {
assertThat(suitability).isEqualTo(expectedEnvironmentalSuitability, offset(0.0000005));
verify(cacheService).saveEnvironmentalSuitabilityCacheEntry(occurrence.getDiseaseGroup().getId(), occurrence.getLocation().getId(), suitability);
} else {
assertThat(suitability).isNull();
}
} finally {
RasterUtils.disposeRaster(suitabilityRaster);
RasterUtils.disposeRasters(adminRasters);
}
}
private DiseaseOccurrence createOccurrence(double x, double y, int gaul, LocationPrecision precision) {
double offsetForRounding = 0.00005;
DiseaseOccurrence occurrence = new DiseaseOccurrence();
Location location = mock(Location.class);
when(location.getGeom()).thenReturn(GeometryUtils.createPoint(x + offsetForRounding, y + offsetForRounding));
when(location.getPrecision()).thenReturn(precision);
if (precision == LocationPrecision.COUNTRY) {
when(location.getCountryGaulCode()).thenReturn(gaul);
} else {
when(location.getAdminUnitQCGaulCode()).thenReturn(gaul);
}
occurrence.setLocation(location);
occurrence.setDiseaseGroup(diseaseGroup);
return occurrence;
}
}