/* * */ package au.org.aurin.wif.impl.demand; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import au.org.aurin.wif.config.ProjectAnalyzer; import au.org.aurin.wif.exception.config.WifInvalidConfigException; import au.org.aurin.wif.exception.validate.IncompleteDemandConfigException; import au.org.aurin.wif.exception.validate.WifInvalidInputException; import au.org.aurin.wif.impl.allocation.comparators.TrendComparator; import au.org.aurin.wif.impl.allocation.comparators.YearComparator; import au.org.aurin.wif.io.GeodataFinder; import au.org.aurin.wif.model.Projection; import au.org.aurin.wif.model.WifProject; import au.org.aurin.wif.model.allocation.AllocationLU; import au.org.aurin.wif.model.demand.CurrentDemographic; import au.org.aurin.wif.model.demand.DemandConfig; import au.org.aurin.wif.model.demand.DemandScenario; import au.org.aurin.wif.model.demand.DemographicData; import au.org.aurin.wif.model.demand.DemographicTrend; import au.org.aurin.wif.model.demand.EmploymentEntry; import au.org.aurin.wif.model.demand.EmploymentGrowthRate; import au.org.aurin.wif.model.demand.EmploymentPastTrendInfo; import au.org.aurin.wif.model.demand.EmploymentSector; import au.org.aurin.wif.model.demand.ResidentialPastTrendInfo; import au.org.aurin.wif.model.demand.data.PreservedData; import au.org.aurin.wif.model.demand.info.DemandInfo; import au.org.aurin.wif.model.demand.info.PreservationDemandInfo; import au.org.aurin.wif.model.demand.info.ResidentialDemandInfo; import au.org.aurin.wif.svc.WifKeys; /** * The Class DemandConfigurator. */ @Component public class DemandConfigurator { /** The Constant serialVersionUID. */ @SuppressWarnings("unused") private static final long serialVersionUID = 21342673455379L; /** The Constant LOGGER. */ private static final Logger LOGGER = LoggerFactory .getLogger(DemandConfigurator.class); /** The required area analyzer. */ @Autowired private RequiredAreaAnalyzer requiredAreaAnalyzer; /** The geodata finder. */ @Autowired private GeodataFinder geodataFinder; /** The project analyzer. */ @Autowired private ProjectAnalyzer projectAnalyzer; /** * Inits the. */ @PostConstruct public void init() { LOGGER.trace("Initializing version: " + WifKeys.WIF_KEY_VERSION); } /** * Cleanup. */ @PreDestroy public void cleanup() { LOGGER.trace(" Service successfully cleared! "); } /** * Creates the automated trends. PRE: User deefined UAZ columns names, * projections//TODO and employment sectors information. * * @param demandConfig * the demand config * @return the demand config * @throws WifInvalidInputException * the wif invalid input exception * @throws WifInvalidConfigException * the wif invalid config exception * @throws IncompleteDemandConfigException * the incomplete demand config exception */ public DemandConfig createAutomatedTrends(final DemandConfig demandConfig) throws WifInvalidInputException, WifInvalidConfigException, IncompleteDemandConfigException { LOGGER.info("createAutomatedTrends for project {}", demandConfig.getProjectId()); DemographicTrend dt = new DemographicTrend(); dt.setLabel(WifKeys.DEFAULT_AUTOMATIC_TREND_NAME); LOGGER.info("creating trend {}", dt.getLabel()); dt.setDemographicData(new HashSet<DemographicData>()); try { dt = this.createResidentialTrendFromPastData(demandConfig, dt); // TODO not yet supported dt = this.createEmploymentTrendFromPastData(demandConfig, dt); demandConfig.getDemographicTrends().add(dt); return demandConfig; } catch (final IncompleteDemandConfigException e) { LOGGER.error("createAutomatedTrends failed!"); throw e; } catch (final WifInvalidInputException e) { final String msg = "createAutomatedTrends failed!"; LOGGER.error(msg); throw new IncompleteDemandConfigException(msg, e); } } /** * Fill basic demand info. * * @param demandConfig * the demand config * @param uazTbl * the uaz tbl * @return the demand config * @throws WifInvalidInputException * the wif invalid input exception * @throws WifInvalidConfigException * the wif invalid config exception * @throws IncompleteDemandConfigException * the incomplete demand config exception */ public DemandConfig fillBasicDemandInfo(final DemandConfig demandConfig, final String uazTbl) throws WifInvalidInputException, WifInvalidConfigException, IncompleteDemandConfigException { LOGGER.info( "fillBasicDemandInfo, beginning Demand configuration for project : {}", demandConfig.getProjectId()); // Updating current demographic from setup information final CurrentDemographic currentDemographic = demandConfig .getCurrentDemographic(); final Integer baseYear = demandConfig.getBaseYear(); currentDemographic.setLabel(baseYear.toString()); currentDemographic.setYear(baseYear); LOGGER.info("Base year: {}", baseYear); final Set<Projection> projections = demandConfig.getProjections(); // Filling up automated information for (final Projection projection : projections) { if (projection.getLabel() == null) { projection.setLabel(projection.getYear().toString()); } } if (demandConfig.getCurrentDemographic().getTotalPopulation() == null) { LOGGER .debug( "Configuring demand information from union geospatial information: {}", uazTbl); final String popFfName = demandConfig .getTotalPopulationFeatureFieldName(); final String clippedEdAreaFfName = demandConfig .getClippedEnumerationDistrictAreaFeatureFieldName(); final String edAreaFfName = demandConfig .getEnumerationDistrictAreaFeatureFieldName(); final String housingUnitsFfName = demandConfig .getNumberOfHousingUnitsFeatureFieldName(); final String householdsFfName = demandConfig .getNumberOfHouseholdsFeatureFieldName(); final long totalPopulation = Math.round(geodataFinder .getNormalisedSumOfDistinctEntriesUAZAttribute(uazTbl, popFfName, clippedEdAreaFfName, edAreaFfName)); final long totalHousingUnits = Math.round(geodataFinder .getNormalisedSumOfDistinctEntriesUAZAttribute(uazTbl, housingUnitsFfName, clippedEdAreaFfName, edAreaFfName)); final long totalHouseholds = Math.round(geodataFinder .getNormalisedSumOfDistinctEntriesUAZAttribute(uazTbl, householdsFfName, clippedEdAreaFfName, edAreaFfName)); demandConfig.getCurrentDemographic().setTotalPopulation(totalPopulation); demandConfig.getCurrentDemographic().setHousingUnits(totalHousingUnits); demandConfig.getCurrentDemographic().setHouseholds( Double.valueOf(totalHouseholds)); final long vacantHousingUnits = totalHousingUnits - totalHouseholds; LOGGER.info("vacantHousingUnits: {}", vacantHousingUnits); demandConfig.getCurrentDemographic().setVacantLand( (double) vacantHousingUnits); } LOGGER.info("totalPopulation: {}", demandConfig.getCurrentDemographic() .getTotalPopulation()); LOGGER.info("totalHousingUnits: {}", demandConfig.getCurrentDemographic() .getHousingUnits()); LOGGER.info("totalHouseholds: {}", demandConfig.getCurrentDemographic() .getHouseholds()); return demandConfig; } /** * Creates the past trends. * * @param wifProject * the wif project * @throws WifInvalidInputException * the wif invalid input exception * @throws WifInvalidConfigException * the wif invalid config exception * @throws IncompleteDemandConfigException * the incomplete demand config exception */ public void createPastTrends(final WifProject wifProject) throws WifInvalidInputException, WifInvalidConfigException, IncompleteDemandConfigException { LOGGER.info("Including trends? : {}", wifProject.getIncludeTrends()); final DemandConfig demandConfig = wifProject.getDemandConfig(); try { if (demandConfig.getCurrentDemographic().getResidentialCurrentData() != null && demandConfig.getCurrentDemographic().getEmploymentCurrentDatas() != null) { // Calculating automated trends if (wifProject.getIncludeTrends()) { DemographicTrend dt = new DemographicTrend(); dt.setLabel(WifKeys.DEFAULT_AUTOMATIC_TREND_NAME); LOGGER.info("Creating trend : {}", dt.getLabel()); dt.setDemographicData(new HashSet<DemographicData>()); dt = createResidentialTrendFromPastData(demandConfig, dt); dt = createEmploymentTrendFromPastData(demandConfig, dt); dt.setWifProject(wifProject); if (wifProject.getDemographicTrends() == null) { final Set<DemographicTrend> dtrends = new HashSet<DemographicTrend>(); dtrends.add(dt); } wifProject.getDemographicTrends().add(dt); } } else { final String msg = "No current demopraphic data exists:"; LOGGER.error(msg); throw new IncompleteDemandConfigException(msg); } } catch (final IncompleteDemandConfigException e) { throw e; } } /** * Creates the residential trend from past data. * * @param demandConfig * the demand config * @param dt * the dt * @return the demographic trend * @throws WifInvalidInputException * the wif invalid input exception * @throws WifInvalidConfigException * the wif invalid config exception * @throws IncompleteDemandConfigException * the incomplete demand config exception */ public DemographicTrend createResidentialTrendFromPastData( DemandConfig demandConfig, DemographicTrend dt) throws WifInvalidInputException, WifInvalidConfigException, IncompleteDemandConfigException { final Set<ResidentialPastTrendInfo> pastTrends = demandConfig .getResidentialPastTrendInfos(); if (pastTrends == null) { throw new IncompleteDemandConfigException( "There are no past trends for this project!"); } if (pastTrends.size() == 0) { throw new IncompleteDemandConfigException( "There are no past trends for this project!"); } LOGGER.info( "Creating residential projected trend: {} from {} past trends : ", dt.getLabel(), pastTrends.size()); for (final ResidentialPastTrendInfo pastTrend : pastTrends) { LOGGER.info("Past trend : {}", pastTrend.getLabel()); } final TreeSet<ResidentialPastTrendInfo> pastTrendsSorted = new TreeSet<ResidentialPastTrendInfo>( new TrendComparator()); pastTrendsSorted.addAll(pastTrends); final TreeSet<Projection> projections = new TreeSet<Projection>( new YearComparator()); projections.addAll(demandConfig.getProjections()); demandConfig = analyseResidentialGrowthTrend(demandConfig, pastTrendsSorted, projections); dt = DemandProjector.projectResidentialDemographicData(dt, projections, demandConfig); return dt; } /** * Creates the employment trend from past data. * * @param demandConfig * the demand config * @param dt * the dt * @return the demographic trend * @throws WifInvalidInputException * the wif invalid input exception * @throws WifInvalidConfigException * the wif invalid config exception * @throws IncompleteDemandConfigException * the incomplete demand config exception */ public DemographicTrend createEmploymentTrendFromPastData( DemandConfig demandConfig, DemographicTrend dt) throws WifInvalidInputException, WifInvalidConfigException, IncompleteDemandConfigException { final Set<EmploymentPastTrendInfo> pastTrends = demandConfig .getEmploymentPastTrendInfos(); if (pastTrends == null) { throw new WifInvalidConfigException( "There are no past employment trends for this project!"); } if (pastTrends.size() == 0) { throw new WifInvalidConfigException( "There are no past employment trends for this project!"); } LOGGER.info( "Creating employment projected trend: {} from {} past trends : ", dt.getLabel(), pastTrends.size()); for (final EmploymentPastTrendInfo pastTrend : pastTrends) { LOGGER.info("Past trend : {}", pastTrend.getLabel()); } final TreeSet<EmploymentPastTrendInfo> trends = new TreeSet<EmploymentPastTrendInfo>( new TrendComparator()); trends.addAll(pastTrends); final TreeSet<Projection> projections = new TreeSet<Projection>( new YearComparator()); projections.addAll(demandConfig.getProjections()); demandConfig.setEmploymentGrowthRates(new HashSet<EmploymentGrowthRate>()); demandConfig = analyseEmploymentGrowthTrend(demandConfig, trends, projections, demandConfig.getSectors()); dt = DemandProjector.projectEmploymentDemographicData(dt, projections, demandConfig, demandConfig.getSectors()); // dt.setWifProject(demandConfig); return dt; } /** * Analyse employment growth trend. * * @param demandConfig * the demand config * @param trends * the trends * @param projections * the projections * @param sectors * the sectors * @return the demand config * @throws WifInvalidInputException * the wif invalid input exception * @throws IncompleteDemandConfigException * the incomplete demand config exception */ private DemandConfig analyseEmploymentGrowthTrend( final DemandConfig demandConfig, final TreeSet<EmploymentPastTrendInfo> trends, final TreeSet<Projection> projections, final Set<EmploymentSector> sectors) throws WifInvalidInputException, IncompleteDemandConfigException { final EmploymentPastTrendInfo baseTrend = trends.first(); final EmploymentPastTrendInfo nextTrend = trends.higher(trends.first()); final Integer observationPeriod = Math.abs(baseTrend.getYear() - trends.higher(trends.first()).getYear()); LOGGER.info("Observation period: {}", observationPeriod); // Projection period (2010 - 2005 = 5) final Integer projectionPeriod = Math.abs(projections.higher( projections.first()).getYear() - projections.first().getYear()); LOGGER.info("Projection period: {}", projectionPeriod); for (final EmploymentSector sector : sectors) { EmploymentEntry baseEmploymentEntry; try { baseEmploymentEntry = baseTrend.getEmploymentEntryBySector(sector); final EmploymentEntry nextEmploymentEntry = nextTrend .getEmploymentEntryBySector(sector); // Get projected total population growth rate final Double growthRate = DemandProjector.projectGrowthRate( projectionPeriod, observationPeriod, baseEmploymentEntry.getEmployees(), nextEmploymentEntry.getEmployees()); LOGGER.info("Projected growth rate for the sector: {}, is: {} ", sector.getLabel(), growthRate); final EmploymentGrowthRate employmentGrowthRate = new EmploymentGrowthRate(); employmentGrowthRate.setDemandConfig(demandConfig); employmentGrowthRate.setSector(sector); employmentGrowthRate.setSectorLabel(sector.getLabel()); employmentGrowthRate.setGrowthRate(growthRate); demandConfig.getEmploymentGrowthRates().add(employmentGrowthRate); } catch (final IncompleteDemandConfigException e) { LOGGER.error("analyseEmploymentGrowthTrend failed: {}", e.getMessage()); throw e; } } return demandConfig; } /** * Analyse residential growth trend. * * @param demandConfig * the demand config * @param pastTrends * the trends * @param projections * the projections * @return the demand config */ private DemandConfig analyseResidentialGrowthTrend( final DemandConfig demandConfig, final TreeSet<ResidentialPastTrendInfo> pastTrends, final TreeSet<Projection> projections) { // Observation period LOGGER.info("analyseResidentialGrowthTrend:number of past trends {}", pastTrends.size()); final ResidentialPastTrendInfo baseTrend = pastTrends.first(); final ResidentialPastTrendInfo nextTrend = pastTrends.higher(pastTrends .first()); final Integer observationPeriod = Math.abs(baseTrend.getYear() - pastTrends.higher(pastTrends.first()).getYear()); LOGGER.info("Observation period: {}", observationPeriod); // Projection period (2010 - 2005 = 5) final Integer projectionPeriod = Math.abs(projections.higher( projections.first()).getYear() - projections.first().getYear()); LOGGER.info("Projection period: {}", projectionPeriod); // Get projected total population growth rate final Double growthRatePopulation = DemandProjector.projectGrowthRate( projectionPeriod, observationPeriod, baseTrend.getTotalPopulation(), nextTrend.getTotalPopulation()); LOGGER.info("Projected growth rate for the total population: {}", growthRatePopulation); demandConfig.setPopulationGrowthRate(growthRatePopulation); // Get projected GQ population growth rate final Double growthRateGQ = DemandProjector.projectGrowthRate( projectionPeriod, observationPeriod, baseTrend.getgQPopulation(), nextTrend.getgQPopulation()); LOGGER.info("Projected growth rate for GQ population: {}", growthRateGQ); demandConfig.setGqGrowthRate(growthRateGQ); // Get projected households growth rate final Double growthRateHouseholds = DemandProjector.projectGrowthRate( projectionPeriod, observationPeriod, baseTrend.getHouseholds(), nextTrend.getHouseholds()); LOGGER.info("Projected growth rate for households: {}", growthRateHouseholds); demandConfig.setHouseholdsGrowthRate(growthRateHouseholds); return demandConfig; } /** * Creates the automated scenario. * * @param demandConfig * the demand config * @param project * the project * @return the demand scenario * @throws WifInvalidInputException * the wif invalid input exception */ public DemandScenario createAutomatedScenario( final DemandConfig demandConfig, final WifProject project) throws WifInvalidInputException { final DemandScenario automatedDemandScenario = new DemandScenario(); LOGGER.info("createAutomatedScenario for : {}", project.getLabel()); final DemographicTrend demographicTrend = demandConfig .getTrendByLabel(WifKeys.DEFAULT_AUTOMATIC_TREND_NAME); automatedDemandScenario .setFeatureFieldName(WifKeys.DEFAULT_DEMAND_SCENARIO_NAME); automatedDemandScenario.setLabel(WifKeys.DEFAULT_DEMAND_SCENARIO_NAME); automatedDemandScenario.setWifProject(project); automatedDemandScenario.setProjectId(project.getId()); automatedDemandScenario.setDemographicTrend(demographicTrend); automatedDemandScenario .setDemographicTrendLabel(WifKeys.DEFAULT_AUTOMATIC_TREND_NAME); LOGGER.info("Using trend: {}", demographicTrend.getLabel()); // FIXME ccreation of demand information is necessary only for employment // calculations? // The information to create Current residential demographic data and // employment // demographic data, is specified in the interface. // To calculate the density, you have to refer to the documentation // Vacant HU = Number of Housing Units – Number of Households // Vacancy Rate = Vacant HU / Total HU // Density = Area / Number of Housing Units // Breakdown/Density = Number of HU / Total number of HU (this total is for // all residential, ie low res, med res, mixed use) // Average Household Size = Population / Number of Households final Double residentialDensity = 1.0; final Double residentialInfillRate = 0.0; final Double residentialBreakdownBType = 0.60; final Double reservedArea = 500.0; final Double vacancyRate = demandConfig.getCurrentDemographic() .getVacancyRate(); final Set<Projection> projections = demandConfig.getProjections(); final Set<AllocationLU> allocationLandUses = project .getAllocationLandUses(); for (final AllocationLU allocationLU : allocationLandUses) { if (allocationLU.isNewPreservation()) { // conservation information demand final PreservationDemandInfo preservedDInfo = new PreservationDemandInfo(); preservedDInfo.setDemandScenario(automatedDemandScenario); automatedDemandScenario.getDemandInfos().add(preservedDInfo); preservedDInfo.setAllocationLUId(allocationLU.getId()); for (final Projection projection : projections) { final PreservedData preservedData = new PreservedData(); preservedData.setReservedArea(reservedArea); preservedData.setProjection(projection); preservedData.setProjectionLabel(projection.getLabel()); preservedDInfo.addProjectedData(preservedData); } } else if (allocationLU.isResidentialLU()) { final ResidentialDemandInfo rdinfo = new ResidentialDemandInfo(); rdinfo.setInfillRate(residentialInfillRate); // rdinfo.setFutureBreakdownByHType(residentialBreakdownBType); rdinfo.setCurrentDensity(residentialDensity); rdinfo.setFutureDensity(residentialDensity); // rdinfo.setFutureVacancyRate(vacancyRate); rdinfo.setResidentialLU(allocationLU); rdinfo.setResidentialLUId(allocationLU.getId()); rdinfo.setDemandScenario(automatedDemandScenario); automatedDemandScenario.getDemandInfos().add(rdinfo); // Associate residential land use demand information final Set<DemandInfo> rsDemandInfos = new HashSet<DemandInfo>(); allocationLU.setDemandInfos(rsDemandInfos); allocationLU.addDemandInfo(rdinfo); } } return automatedDemandScenario; } }