package au.org.aurin.wif.impl.lsa.scoring;
import java.util.Collection;
import org.opengis.feature.simple.SimpleFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import au.org.aurin.wif.exception.config.WifInvalidConfigException;
import au.org.aurin.wif.exception.validate.WifInvalidInputException;
import au.org.aurin.wif.model.WifProject;
import au.org.aurin.wif.model.allocation.AllocationLU;
import au.org.aurin.wif.model.suitability.Factor;
import au.org.aurin.wif.model.suitability.FactorImportance;
import au.org.aurin.wif.model.suitability.FactorTypeRating;
import au.org.aurin.wif.model.suitability.SuitabilityLU;
import au.org.aurin.wif.model.suitability.SuitabilityRule;
import au.org.aurin.wif.model.suitability.SuitabilityScenario;
/**
* <b>LSAScoring.java</b> : includes logic for discovering land suitability
* analysis
*
* @author <a href="mailto:marcosnr@unimelb.edu.au"> Marcos Nino-Ruiz
* marcosnr@unimelb.edu.au</a> - 2012
*/
public class LSAScoring {
/**
* logger.
*/
private static final Logger LOGGER = LoggerFactory
.getLogger(LSAScoring.class);
/**
* Calculates suitability score and categories according to a list of
* suitability factors in a scenario. In the wif Documentations says that: The
* suitability scores are computed by multiplying the importance weight for
* each suitability factor by its suitability ratings and summing the
* products. These scores are used to determine the relative suitability of
* different locations for each future land use. If a feature is not
* developable (like Water), it will not be scored, likewise if the feature is
* not convertable (e.g. is Residential already), or not suitable (on factor
* rating was marked as 0, i.e. to exclude). products. TODO Algorithm is not
* optimized and makes A LOT of loops, temporal while we get the logic right!
*
* @param uazFeature
* the uaz feature
* @param uazLandUseValueObj
* the uaz land use value obj
* @param existingLandUses
* the existing land uses
* @param suitabilityScn
* the suitability scn
* @param suitabilityLU
* the suitability lu
* @param project
* the project
* @return ScoredSimpleFeature with the analysis
* @throws WifInvalidConfigException
* the wif invalid config exception
* @throws WifInvalidInputException
* the wif invalid input exception
*
*/
public static Double scoreSuitability(SimpleFeature uazFeature,
Object uazLandUseValueObj, Collection<AllocationLU> existingLandUses,
SuitabilityScenario suitabilityScn, SuitabilityLU suitabilityLU,
WifProject project, Boolean isProductionLevel)
throws WifInvalidConfigException, WifInvalidInputException {
Double lsaScore = Double.valueOf(0);
String uazLandUseValue = "";
// FIXME we have casting issues,they are not relevant to suitability
// scoring, and code is replicated?
if (uazLandUseValueObj instanceof Double) {
LOGGER.trace("transforming a double value");
uazLandUseValue = uazLandUseValueObj.toString();
}
if (uazLandUseValueObj instanceof Integer) {
LOGGER.trace("transforming an integer value ");
uazLandUseValue = uazLandUseValueObj.toString();
} else if (uazLandUseValueObj instanceof String) {
LOGGER.trace("string value, no need to transform");
uazLandUseValue = uazLandUseValueObj.toString();
} else {
// TODO checked before entering this function!
// LOGGER.warn(" the type of the uaz feature for ALU is not recognized, the analysis may not be correct!");
LOGGER.trace("assuming the land use value is a string");
uazLandUseValue = uazLandUseValueObj.toString();
}
if (isLandUseNotDevelopable(uazLandUseValue, existingLandUses)) {
LOGGER.trace("feature is not developable, checking the next UAZ feature");
lsaScore = project.getSuitabilityConfig().getNotDevelopableScore();
} else {
lsaScore = Double.valueOf(0);
LOGGER.trace("scoring feature for suitabilityLU: {}",
suitabilityLU.getLabel());
SuitabilityRule rule = suitabilityScn
.getLandUseConversionBySLUName(suitabilityLU.getLabel());
LOGGER.trace("{} has {} conversions", suitabilityLU.getLabel(), rule
.getConvertibleLUs().size());
Collection<FactorImportance> factorsImp = rule.getFactorImportances();
if (!isLandUseConvertable(uazLandUseValue, rule.getConvertibleLUs())) {
LOGGER.trace("UAZ value of {} is not convertable to {}",
uazLandUseValue, suitabilityLU.getLabel());
lsaScore = project.getSuitabilityConfig().getNotConvertableScore();
} else if (isLandUseNotSuitable(uazFeature, factorsImp)) {
lsaScore = project.getSuitabilityConfig().getNotSuitableScore();
} else {
for (FactorImportance factorImportance : factorsImp) {
Factor aFactor = factorImportance.getFactor();
String factorName = aFactor.getFeatureFieldName();
String factorLabel = aFactor.getLabel();
Double factorWeight = factorImportance.getImportance();
LOGGER.trace(" Factor label: {}", factorLabel);
LOGGER.trace(" Factor UAZ column attribute name: {}", factorName);
LOGGER.trace("- Factor importance: {}", factorWeight);
// LOGGER.trace("factor id: {}", factorImportance.getId());
Object uazFactorValueObj = uazFeature.getAttribute(factorName);
String uazFactorValue = getStringValue(uazFactorValueObj);
LOGGER.trace("-= uazFactorValue = {}", uazFactorValue);
for (FactorTypeRating aFactorRating : factorImportance
.getFactorTypeRatings()) {
Object ratingValue = aFactorRating.getFactorType().getValue();
Double ratingScore = aFactorRating.getScore();
String ratingLabel = aFactorRating.getFactorType().getLabel();
LOGGER.trace("--> for factor type label: {}", ratingLabel);
LOGGER.trace("--> rating UAZ value: {}", ratingValue);
if (ratingValue.equals(uazFactorValue)) {
LOGGER.trace("--> the rating is: {}", ratingScore);
lsaScore += (ratingScore * factorWeight);
LOGGER.trace("....... current lsaScore is = {}", lsaScore);
} else {
LOGGER
.trace(
"##= the {} rating UAZ value doesn't match the uazFactorValue of {}, checking more...",
ratingValue, uazFactorValue);
}
}
}
}
}
if (isProductionLevel) {
LOGGER.info("********* Final lsa score for {} is: {} ",
suitabilityLU.getLabel(), lsaScore);
}
return lsaScore;
}
/**
* Determines if the land is not suitable, i.e. one factor has a rating of 0
* TODO IT HAS TO BE DONE HERE BECAUSE OTHERWISE WE WOULD break the for loop
* in the factors but the algorithm could be better
*
* @param uazFeature
* the uaz feature
* @param factorsImp
* the factors imp
* @return true, if is land use not suitable
*/
static boolean isLandUseNotSuitable(SimpleFeature uazFeature,
Collection<FactorImportance> factorsImp) {
for (FactorImportance fi : factorsImp) {
Factor aSuitabilityFactor = fi.getFactor();
String factorName = aSuitabilityFactor.getFeatureFieldName();
String factorLabel = aSuitabilityFactor.getLabel();
// TODO Casting issues, USE instanceof!
Object uazFactorValueObj = uazFeature.getAttribute(factorName);
String uazFactorValue = getStringValue(uazFactorValueObj);
for (FactorTypeRating aFactorRating : fi.getFactorTypeRatings()) {
Object ratingValue = aFactorRating.getFactorType().getValue();
Double ratingScore = aFactorRating.getScore();
if (ratingValue.equals(uazFactorValue) && (ratingScore == 0d)
&& (fi.getImportance() != 0)) {
LOGGER
.trace(
"....{} -> {} is excluded from this suitability land use! (rating is 0) ",
factorLabel, aFactorRating.getFactorType().getLabel());
return true;
}
}
}
return false;
}
/**
* TODO this dirty, method is to solve casting issuesGets the string value.
* subject to be better
*
* @param uazFactorValueObj
* the uaz factor value obj
* @return the string value
*/
static String getStringValue(Object uazFactorValueObj) {
String uazFactorValue;
if (uazFactorValueObj instanceof Double) {
LOGGER.trace("transforming a double value");
uazFactorValue = uazFactorValueObj.toString();
}
if (uazFactorValueObj instanceof Integer) {
LOGGER.trace("transforming an integer value ");
uazFactorValue = uazFactorValueObj.toString();
} else if (uazFactorValueObj instanceof String) {
LOGGER.trace("string value, no need to transform");
uazFactorValue = uazFactorValueObj.toString();
} else {
// TODO checked before entering this function!
// LOGGER.warn(" the type of the uaz feature for ALU is not recognized, the analysis may not be correct!");
LOGGER.trace("assuming the factor value is a string");
uazFactorValue = uazFactorValueObj.toString();
}
return uazFactorValue;
}
/**
* Determines if the land is convertable, i.e. for Undeveloped land use can be
* made Residential
*
* @param uazLandUseValue
* the uaz land use value
* @param conversions
* .
* @return true, if is land use convertable
* @throws WifInvalidConfigException
* the wif invalid config exception
*/
static boolean isLandUseConvertable(Object uazLandUseValue,
Collection<AllocationLU> conversions) throws WifInvalidConfigException {
if (conversions.size() == 0) {
throw new WifInvalidConfigException(
"no convertible ALUs for this land use, pointless Suitability Analysis!?");
}
for (AllocationLU aLU : conversions) {
LOGGER.trace(
"checking convertability into this SLU of ALU {} / UAZ value {}",
aLU.getLabel(), aLU.getLabel());
if (aLU.getFeatureFieldName().equals(uazLandUseValue)) {
return true;
}
}
return false;
}
/**
* Determines if the land is not developable, i.e. "Water" or "Right of Way"
*
* @param uazLandUseValue
* the uaz land use value
* @param existingLUs
* the existing l us
* @return true, if is land use not developable
*/
static boolean isLandUseNotDevelopable(Object uazLandUseValue,
Collection<AllocationLU> existingLUs) {
for (AllocationLU aCurrLU : existingLUs) {
if (uazLandUseValue.equals(aCurrLU.getFeatureFieldName())) {
LOGGER
.trace(
" land use UAZ value {}, associated to ALU label: {}, is developable! ",
uazLandUseValue, aCurrLU.getLabel());
return aCurrLU.isNotDevelopable();
}
}
LOGGER
.warn(
"could not find land use:{} in existing land uses to check if is not developable, assuming is not!",
uazLandUseValue);
return true;
}
}