package uk.ac.ox.zoo.seeg.abraid.mp.dataacquisition.qc; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.Country; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.Location; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.LocationPrecision; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.GeometryService; import static java.lang.Math.log; /** * Manages processes that run after QC but before the location is written to the database. * * Copyright (c) 2014 University of Oxford */ public class PostQCManager { private static final double MAX_AREA_FOR_MODEL_ELIGIBLE_COUNTRY = 115000.0; private static final double PIXEL_EQUIVALENT_AREA = 25.0; // ALPHA & BETA chosen such that f(25)=1 & f(115000)=0 private static final double ALPHA = 1.0 / log(PIXEL_EQUIVALENT_AREA / MAX_AREA_FOR_MODEL_ELIGIBLE_COUNTRY); private static final double BETA = 1.0 / MAX_AREA_FOR_MODEL_ELIGIBLE_COUNTRY; private final GeometryService geometryService; private final QCLookupData qcLookupData; public PostQCManager(GeometryService geometryService, QCLookupData qcLookupData) { this.geometryService = geometryService; this.qcLookupData = qcLookupData; } /** * Runs all post-QC processes on the specified location. * @param location The location */ public void runPostQCProcesses(Location location) { assignDiseaseExtentAdminUnits(location); assignCountry(location); failQCIfNotOnLand(location); failQCIfAnyGaulCodesAreMissing(location); setResolutionWeighting(location); setModelEligibility(location); } /** * Finds and assigns the disease extent admin units that contain the location's point. * @param location The location. */ private void assignDiseaseExtentAdminUnits(Location location) { validateLocation(location); // Find and assign the disease extent admin units that contain the location's point Integer adminUnitGlobalGaulCode = geometryService.findAdminUnitGlobalThatContainsPoint(location.getGeom()); location.setAdminUnitGlobalGaulCode(adminUnitGlobalGaulCode); Integer adminUnitTropicalGaulCode = geometryService.findAdminUnitTropicalThatContainsPoint(location.getGeom()); location.setAdminUnitTropicalGaulCode(adminUnitTropicalGaulCode); } /** * Find and assign the country that contains the location's point. * @param location The location. */ private void assignCountry(Location location) { Country country = geometryService.findCountryThatContainsPoint(location.getGeom()); location.setCountry(country); } private void failQCIfNotOnLand(Location location) { // A sanity check - this should only happen if, after QC stage 2, the point is adjusted to be off land if (!geometryService.doesLandSeaBorderContainPoint(location.getGeom())) { location.setHasPassedQc(false); } } private void failQCIfAnyGaulCodesAreMissing(Location location) { // A sanity check - this should only happen if the shapefiles are inconsistent or the snapping routines fail if (location.getAdminUnitGlobalGaulCode() == null || location.getAdminUnitTropicalGaulCode() == null || location.getCountryGaulCode() == null) { location.setHasPassedQc(false); } } private void setResolutionWeighting(Location location) { if (location.hasPassedQc()) { location.setResolutionWeighting(calculateResolutionWeighting(location)); } else { location.setResolutionWeighting(null); } } private Double calculateResolutionWeighting(Location location) { LocationPrecision precision = location.getPrecision(); if (precision.equals(LocationPrecision.PRECISE)) { return 1.0; } else { double area = extractArea(location); if (area <= PIXEL_EQUIVALENT_AREA) { return 1.0; } else if (area >= MAX_AREA_FOR_MODEL_ELIGIBLE_COUNTRY) { return 0.0; } else { // ALPHA & BETA chosen such that f(25)=1 & f(115000)=0 // http://fooplot.com/plot/daijmnjedc return ALPHA * log(BETA * area); } } } private double extractArea(Location location) { if (location.getPrecision().equals(LocationPrecision.COUNTRY)) { return qcLookupData.getCountryMap().get(location.getCountryGaulCode()).getArea(); } else { return qcLookupData.getAdminUnitsMap().get(location.getAdminUnitQCGaulCode()).getArea(); } } private void setModelEligibility(Location location) { if (location.hasPassedQc()) { if (location.getPrecision().equals(LocationPrecision.COUNTRY)) { Double area = extractArea(location); location.setIsModelEligible(area <= MAX_AREA_FOR_MODEL_ELIGIBLE_COUNTRY); } else { location.setIsModelEligible(true); } } else { location.setIsModelEligible(false); } } private void validateLocation(Location location) { if (location.getGeom() == null || location.getPrecision() == null) { throw new IllegalArgumentException("Location must have a point and a precision"); } } }