package au.org.aurin.wif.impl.lsa; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.geotools.data.DataStore; import org.geotools.data.DefaultTransaction; import org.geotools.data.FeatureWriter; import org.geotools.data.Query; import org.geotools.data.Transaction; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.feature.DefaultFeatureCollection; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.filter.text.cql2.CQLException; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Filter; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.operation.TransformException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.vividsolutions.jts.io.ParseException; import au.org.aurin.wif.config.GeoServerConfig; import au.org.aurin.wif.config.ProjectConfigurator; import au.org.aurin.wif.config.WifConfig; import au.org.aurin.wif.exception.config.WifInvalidConfigException; import au.org.aurin.wif.exception.io.DatabaseFailedException; import au.org.aurin.wif.exception.validate.SuitabilityAnalysisFailedException; import au.org.aurin.wif.exception.validate.WifInvalidInputException; import au.org.aurin.wif.impl.lsa.scoring.LSAScoring; import au.org.aurin.wif.impl.report.ConsoleReporter; import au.org.aurin.wif.io.DataSourceFactory; import au.org.aurin.wif.io.GeodataFilterer; import au.org.aurin.wif.io.GeodataFinder; import au.org.aurin.wif.io.GeospatialDataSource; import au.org.aurin.wif.io.PostgisDataStoreConfig; import au.org.aurin.wif.model.WifProject; import au.org.aurin.wif.model.allocation.AllocationLU; import au.org.aurin.wif.model.suitability.FactorImportance; import au.org.aurin.wif.model.suitability.SuitabilityLU; import au.org.aurin.wif.model.suitability.SuitabilityRule; import au.org.aurin.wif.model.suitability.SuitabilityScenario; import au.org.aurin.wif.repo.suitability.SuitabilityScenarioDao; import au.org.aurin.wif.svc.WifKeys; /** * <b>SuitabilityAnalyzer.java</b> : Implementation of @see WifAnalysisService * * * @author <a href="mailto:marcosnr@unimelb.edu.au"> Marcos Nino-Ruiz * marcosnr@unimelb.edu.au</a> - 2012 */ @Component public class SuitabilityAnalyzer { /** The Constant serialVersionUID. */ @SuppressWarnings("unused") private static final long serialVersionUID = 213426734553L; /** * logger. */ private static final Logger LOGGER = LoggerFactory .getLogger(SuitabilityAnalyzer.class); /** The geodata finder. */ @Autowired private GeodataFinder geodataFinder; /** The reporter. */ @Autowired private ConsoleReporter reporter; /** The geoserver config. */ @Autowired private GeoServerConfig geoserverConfig; /** The geodata filterer. */ @Autowired private GeodataFilterer geodataFilterer; /** The wif config. */ @Autowired private WifConfig wifConfig; /** The project configurator. */ @Autowired private ProjectConfigurator projectConfigurator; /** The wif suitabilityScenario dao. */ @Autowired private SuitabilityScenarioDao suitabilityScenarioDao; @Autowired @Qualifier(value = "wifDataStoreConfig") private PostgisDataStoreConfig postgisDataStoreConfig; @Autowired private DataSourceFactory dataSourceFactory; /** * to handle initialization. */ @PostConstruct public void init() { LOGGER.trace("Initializing version: " + WifKeys.WIF_KEY_VERSION); } /** * to handle destroy. */ @PreDestroy public void cleanup() { LOGGER.trace(" Service succesfully cleared! "); } /** * Performs the suitability analysis based on the suitability scenario, and * store them in a geospatial database accessible to geoserver. The * suitability analysis show the spatial distribution of the various factors * (slopes, prime agricultural soils, etc.) that can be used to analyse the * suitability of different locations to accommodate future land use demands * in the Suitability component. the results of the land allocation analysis * is stored directly in the provided unified areas zone * * @param suitabilityScenario * the suitability scenario * @param areaAnalyzed * the area analyzed * @param crsArea * the crs area * @return the wMS outcome * @throws WifInvalidConfigException * the wif invalid config exception * @throws WifInvalidInputException * the wif invalid input exception * @throws NoSuchAuthorityCodeException * the no such authority code exception * @throws FactoryException * the factory exception * @throws MismatchedDimensionException * the mismatched dimension exception * @throws TransformException * the transform exception * @throws ParseException * the parse exception * @throws IOException * Signals that an I/O exception has occurred. * @throws SuitabilityAnalysisFailedException * the wif analysis failed exception */ public Boolean doSuitabilityAnalysisWMS( final SuitabilityScenario suitabilityScenario, final String areaAnalyzed, final String crsArea) throws WifInvalidConfigException, WifInvalidInputException, NoSuchAuthorityCodeException, FactoryException, MismatchedDimensionException, TransformException, ParseException, IOException, SuitabilityAnalysisFailedException { if (wifConfig.isProductionLevel()) { LOGGER.debug("doSuitabilityAnalysisWMS suitability analysis of == {}", suitabilityScenario.getLabel()); } final WifProject wifProject = suitabilityScenario.getWifProject(); final String uazDBTable = wifProject.getSuitabilityConfig() .getUnifiedAreaZone(); if (wifConfig.isProductionLevel()) { LOGGER.debug("uazDBTable: {}", uazDBTable); } final String crsProject = wifProject.getSrs(); if (wifConfig.isProductionLevel()) { LOGGER.debug("crsProject: {}", crsProject); } final String geometryColumnName = wifProject.getGeometryColumnName(); if (wifConfig.isProductionLevel()) { LOGGER.debug("geometryColumnName: {}", geometryColumnName); } final Collection<AllocationLU> existingLandUses = wifProject .getAllocationLandUses(); final Collection<SuitabilityLU> suitabilityLUs = wifProject .getSuitabilityLUs(); DataStore wifDataStore = null; final Transaction transaction = new DefaultTransaction("update"); FeatureWriter<SimpleFeatureType, SimpleFeature> writer = null; int featureCounter = 1; try { // TODO maybe from parameters as well in a later stage if (wifConfig.isProductionLevel()) { if (wifConfig.isProductionLevel()) { LOGGER.debug("Attempting to open the datastore..."); // commented by ali // wifDataStore = geodataFinder.openPostgisDataStore(); } } final Map<String, Object> wifParameters = new HashMap<String, Object>(); wifParameters.put(WifKeys.POLYGON, areaAnalyzed); wifParameters.put(WifKeys.CRS_ORG, crsArea); wifParameters.put(WifKeys.CRS_DEST, crsProject); wifParameters.put(WifKeys.GEOMETRY_COLUMN_KEY, geometryColumnName); final Filter filter = geodataFilterer .getFilterFromParameters(wifParameters); // commented by ali // SimpleFeatureSource featureSourceUD = wifDataStore // .getFeatureSource(uazDBTable); // ali - instead of using autowired bean; I declare a new one, since // autowired bean it is not updated. GeospatialDataSource wifDataSource; wifDataSource = dataSourceFactory .createGeospatialDataSource(postgisDataStoreConfig .getDataStoreParams()); wifDataStore = wifDataSource.getDataStore(); final SimpleFeatureSource featureSourceUD = wifDataStore .getFeatureSource(uazDBTable); writer = wifDataStore.getFeatureWriter(uazDBTable, filter, transaction); // end ali LOGGER.info("calculating the number of rows for analysis..."); final Query query = new Query(geometryColumnName, filter); final int count = featureSourceUD.getCount(query); LOGGER.info( "Attempting to get the feature writer to modify {} features...", count); // commented by ali // writer = wifDataStore.getFeatureWriter(uazDBTable, filter, // transaction); LOGGER .debug("Feature writer successfully obtained! Proceeding to performing analysis..."); for (final SuitabilityLU suitabilityLU : suitabilityLUs) { if (wifConfig.isProductionLevel()) { LOGGER.debug("Using suitabilityLU.getFeatureFieldName(): {}", suitabilityLU.getFeatureFieldName()); } if (wifConfig.isProductionLevel()) { LOGGER.debug("--> Analysing suitabilityLU {} ,scorecolumnlabel: {}", suitabilityLU.getLabel(), suitabilityLU.getFeatureFieldName()); } } while (writer.hasNext()) { final SimpleFeature uazFeature = writer.next(); // trying to solve caching/synchronicity issue if (featureCounter == 0) { Thread.sleep(3000); } // for lengthy analysis to know that the analysis is still going if (featureCounter % 3000 == 0) { LOGGER.info("--> Analysed {} features so far...", featureCounter); } LOGGER.trace("Analysing feature = {} ", featureCounter); // LOGGER.trace("Analysing Suitability of uaz id: {}", // uazFeature.getID()); // test ali // System.out.println(uazFeature.getAttribute("score_lblnew2")); final Object uazLandUseValueObj = uazFeature.getAttribute(wifProject .getExistingLUAttributeName()); // TODO Commented because geotools problem with logging levels weird // NullPointerException in a caching/synchronicity issue for // uazFeature.setAttribute // LOGGER.trace("--> Existing UAZ LandUse value= {}", // uazLandUseValueObj); // LOGGER.trace("--> Analysing {} suitabilities ", // suitabilityLUs.size()); for (final SuitabilityLU suitabilityLU : suitabilityLUs) { if (suitabilityScenario.getLandUseConversionBySLUName(suitabilityLU .getLabel()) == null) { // LOGGER.trace("No suitability rules defined for suitabilityLU: {}", // suitabilityLU.getLabel()); } else { final Double scoreSuitability = LSAScoring.scoreSuitability( uazFeature, uazLandUseValueObj, existingLandUses, suitabilityScenario, suitabilityLU, wifProject, wifConfig.isProductionLevel()); try { LOGGER.trace(suitabilityLU.getFeatureFieldName()); LOGGER.trace(scoreSuitability.toString()); uazFeature.setAttribute(suitabilityLU.getFeatureFieldName(), scoreSuitability); } catch (final Exception e) { if (featureCounter < 10) { LOGGER .warn("caching/synchronicity issue, trying to recover..."); Thread.sleep(3000); continue; } else { throw e; } } } } // LOGGER.trace("--> Finished analysis of {} writing feature ", // uazFeature.getID()); writer.write(); featureCounter++; } LOGGER .info( "attempting to modify the last features of {} features in : {}... it might take a while", featureCounter, uazDBTable); final Long timeelapsed = System.currentTimeMillis() / 1000 % 60; transaction.commit(); LOGGER.info("Transaction took {} seconds to perform, in : {}", timeelapsed, uazDBTable); } catch (final IOException e) { LOGGER .error("IOException in updating acccess failed: {}", e.getMessage()); LOGGER.error("WMSOutcome could not be created"); throw new SuitabilityAnalysisFailedException( "WMSOutcome could not be created ", e); } catch (final WifInvalidConfigException e) { LOGGER.error("WifInvalidConfigException configuration failed: {}", e.getMessage()); LOGGER.error("WMSOutcome could not be created"); throw e; } catch (final CQLException e) { LOGGER.error("CQLException filter failed: {}", e.getMessage()); LOGGER.error("WMSOutcome could not be created"); throw new SuitabilityAnalysisFailedException( "WMSOutcome could not be created ", e); } catch (final Exception e) { LOGGER.error("Exception: {}", e.getMessage()); LOGGER.error("WMSOutcome could not be created"); throw new SuitabilityAnalysisFailedException( "WMSOutcome could not be created ", e); } finally { LOGGER.info("modified {} features,closing opened geofeatures in : {}", featureCounter, uazDBTable); if (writer != null) { writer.close(); // IMPORTANT } transaction.close(); // ali // wifDataStore.dispose(); // ali } /////////////////////////////////////////////////////////////////////////// //new code for transforming suitablity scores // final SuitabilityConfig suitabilityConfig = wifProject.getSuitabilityConfig(); // // LOGGER.info("Transforming Suitability Scores."); // for (final SuitabilityLU slu : suitabilityLUs) { // final Double minv = geodataFinder.getScoreRanges("min", // suitabilityConfig.getUnifiedAreaZone(), slu.getFeatureFieldName()); // final Double maxv = geodataFinder.getScoreRanges("max", // suitabilityConfig.getUnifiedAreaZone(), slu.getFeatureFieldName()); // geodataFinder.TransformSuitabilityScore(suitabilityConfig.getUnifiedAreaZone(), slu.getFeatureFieldName(),minv,maxv); // } /////////////////////////////////////////////////////////////////////////// LOGGER.info("analysis process finished successfully for {}", uazDBTable); suitabilityScenarioDao.updateSuitabilityScenarioStatus(suitabilityScenario, Boolean.TRUE); return Boolean.TRUE; } /** * Performs the suitability analysis based on the SuitabilityScenario. The * suitability analysis show the spatial distribution of the various factors * (slopes, prime agricultural soils, etc.) that can be used to analyse the * suitability of different locations to accommodate future land use demands * in the Suitability component * * @param suitabilityScenario * the suitability scenario * @param areaAnalyzed * the area analyzed * @param crsArea * the crs area * @return the simple feature collection with the modified analysis * @throws WifInvalidConfigException * the wif invalid config exception * @throws WifInvalidInputException * the wif invalid input exception * @throws NoSuchAuthorityCodeException * the no such authority code exception * @throws FactoryException * the factory exception * @throws MismatchedDimensionException * the mismatched dimension exception * @throws TransformException * the transform exception * @throws ParseException * the parse exception * @throws CQLException * the cQL exception * @throws DatabaseFailedException */ public SimpleFeatureCollection doSuitabilityAnalysis( final SuitabilityScenario suitabilityScenario, final String areaAnalyzed, final String crsArea) throws WifInvalidConfigException, WifInvalidInputException, NoSuchAuthorityCodeException, FactoryException, MismatchedDimensionException, TransformException, ParseException, CQLException, DatabaseFailedException { if (wifConfig.isProductionLevel()) { LOGGER.debug("processing suitability analysis for ={}", suitabilityScenario.getLabel()); } final WifProject wifProject = suitabilityScenario.getWifProject(); final String uazDBTable = wifProject.getSuitabilityConfig() .getUnifiedAreaZone(); if (wifConfig.isProductionLevel()) { LOGGER.debug("uazDBTable: {}", uazDBTable); } final String crsProject = wifProject.getSrs(); final List<String> suitabilityColumns = new ArrayList<String>(wifProject .getSuitabilityConfig().getSuitabilityColumns()); if (wifConfig.isProductionLevel()) { LOGGER.debug("crsProject: {}", crsProject); } final Collection<AllocationLU> existingLandUses = wifProject .getAllocationLandUses(); final Collection<SuitabilityLU> suitabilityLUs = wifProject .getSuitabilityLUs(); LOGGER.info("Suitability Scenario Score UAZ column name: {}", suitabilityScenario.getLabel()); // SimpleFeatureCollection lsaCollection = // FeatureCollections.newCollection(); final DefaultFeatureCollection lsaCollection = new DefaultFeatureCollection(); final Map<String, Object> wifParameters = new HashMap<String, Object>(); wifParameters.put(WifKeys.POLYGON, areaAnalyzed); wifParameters.put(WifKeys.CRS_ORG, crsArea); wifParameters.put(WifKeys.CRS_DEST, crsProject); final Filter filter = geodataFilterer .getFilterFromParameters(wifParameters); final SimpleFeatureCollection uazCollection = geodataFinder .getFeatureCollectionfromDB(uazDBTable, filter, suitabilityColumns); final SimpleFeatureIterator it = uazCollection.features(); try { final SimpleFeatureType uazFeatureType = uazCollection.getSchema(); final SimpleFeatureTypeBuilder sftb = new SimpleFeatureTypeBuilder(); sftb.init(uazFeatureType); sftb.setName(suitabilityScenario.getLabel()); final SimpleFeatureType lsaFeatureType = sftb.buildFeatureType(); while (it.hasNext()) { final SimpleFeature uazFeature = it.next(); LOGGER.info("Analysing Suitability of id: {}", uazFeature.getID()); final Object uazLandUseValueObj = uazFeature.getAttribute(wifProject .getExistingLUAttributeName()); if (wifConfig.isProductionLevel()) { LOGGER .debug("--> Existing UAZ LandUse value= {}", uazLandUseValueObj); // TODO Maybe later will be necessary for performance // SimpleFeatureBuilder sfb = new // SimpleFeatureBuilder(lsaFeatureType); // sfb.addAll(uazFeature.getAttributes()); // SimpleFeature lsaFeature = sfb.buildFeature(null); } for (final SuitabilityLU suitabilityLU : suitabilityLUs) { final Double scoreSuitability = LSAScoring.scoreSuitability( uazFeature, uazLandUseValueObj, existingLandUses, suitabilityScenario, suitabilityLU, wifProject, wifConfig.isProductionLevel()); uazFeature.setAttribute(suitabilityLU.getFeatureFieldName(), scoreSuitability); } lsaCollection.add(uazFeature); } if (wifConfig.isProductionLevel()) { LOGGER.debug("total number of modified features: {}", lsaCollection.size()); } } catch (final WifInvalidConfigException e) { LOGGER.error("doSuitabilityAnalysis configuration failed: {}", e.getMessage()); throw e; } finally { it.close(); // IMPORTANT } return lsaCollection; } /** * Generate score ranges. * * @param suitabilityScenario * the suitability scenario * @return the map */ public Map<String, Integer> generateScoreRanges( final SuitabilityScenario suitabilityScenario) { if (wifConfig.isProductionLevel()) { LOGGER.debug("generateScoreRanges for: {}", suitabilityScenario.getLabel()); } final Map<String, Integer> scoreColumns = new HashMap<String, Integer>(); final Set<SuitabilityRule> suitabilityRules = suitabilityScenario .getSuitabilityRules(); for (final SuitabilityRule suitabilityRule : suitabilityRules) { final SuitabilityLU suitabilityLU = suitabilityRule.getSuitabilityLU(); final Set<FactorImportance> factorImportances = suitabilityRule .getFactorImportances(); Double totalRange = 0.0; for (final FactorImportance factorImportance : factorImportances) { if (factorImportance.getImportance() != 0.0) { if (wifConfig.isProductionLevel()) { LOGGER.debug("range for {} is {}", factorImportance.getFactor() .getLabel(), factorImportance.getImportance() * 100); } totalRange += factorImportance.getImportance() * 100; } } if (wifConfig.isProductionLevel()) { LOGGER.debug("totalRange for {} is {}", suitabilityLU.getLabel(), totalRange); } scoreColumns.put(suitabilityLU.getFeatureFieldName(), totalRange.intValue()); } return scoreColumns; } }