VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "ThisDocument" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = True Attribute VB_Exposed = True 'Raster-Based Dasymetric Mapping Routine 'Developed at the University of Colorado in Boulder 'by Torrin Hultgren and Jeremy Mennis 'Apologies for the lack of built-in user interaction 'I don't have the time to code it right now, and since the future lies in python scripts in ModelBuilder, 'if I did have the time, I'd spend it on porting this script to python rather than coding something clumsy Public Sub Dasymetric_Raster() On Error GoTo ERH Dim pMXDoc As esriArcMapUI.IMxDocument Set pMXDoc = ThisDocument 'These are the user defined variables, please change as necessary: Dim Percent As Variant 'This is how you set the selection criteria for representative source units 'It is the percent of the source unit that must be covered by an ancillary class (from 0 to 1) Percent = 0.9 Dim popFieldName As Variant 'This is the name of the field in your population dataset that contains population count data popFieldName = "POP2000" Dim PresetClasses As New Collection Dim PresetDensities As New Collection 'This is how you preset certain classes - for example, a water class usually has 0 population 'Put your ancillary class code/value in the quotes - in the example, the water class has a raster value of 5 PresetClasses.Add "5" 'Water 'Put your preset density in the first quotes, and the above class in the second quotes PresetDensities.Add "0", "5" PresetClasses.Add "3" 'Non-residential Developed PresetDensities.Add "0", "3" 'You may cut and paste the above lines as many times as necessary to include all classes Dim PathName As String 'Please type the pathname where the input data are stored PathName = "C:\users\mhessenflow\documents\rachels_dasymetric\output2" Dim pPopLayer As esriCarto.ILayer 'This is how you designate which layers in the table of contents are your input data 'Arc numbers the layers from top to bottom starting with zero. 'You can either put the vector population layer first and the raster ancillary layer second 'Or just put the appropriate numbers in the parentheses below. Set pPopLayer = pMXDoc.FocusMap.Layer(0) Dim pAncLayer As esriCarto.ILayer Set pAncLayer = pMXDoc.FocusMap.Layer(1) 'End of user defined variables 'Time the script for reference purposes Dim StartTime As Variant Dim EndTime As Variant Dim Elapsed As Variant StartTime = Now Dim pErrorTrack As String pErrorTrack = "Initial Raster Procedures" Dim pConvOp As esriGeoAnalyst.IConversionOp ' Set pConvOp = New esriGeoAnalyst.RasterConversionOp ' !!! MLH We set the pixel extent for the raster environment below, ' but we have already created this operator. Does that cause ' the problem with undefined pixel extents on the first run? Dim pPopFeatureLayer As esriCarto.IFeatureLayer Set pPopFeatureLayer = pPopLayer Dim pPopFeatureClass As esriGeoDatabase.IFeatureClass Set pPopFeatureClass = pPopFeatureLayer.FeatureClass Dim pPopGeoDataset As esriGeoDatabase.IGeoDataset Set pPopGeoDataset = pPopFeatureClass Dim pRasterLayer As esriCarto.IRasterLayer Set pRasterLayer = pAncLayer Dim pAncRaster As esriGeoDatabase.IRaster Set pAncRaster = pRasterLayer.Raster ' Get cell from pInputRaster and extent from pop dataset Dim pRasterProp As esriDataSourcesRaster.IRasterProps Dim pExtent As esriGeometry.IEnvelope Dim vCell As Double vCell = 5 'default value to avoid problems... Set pRasterProp = pAncRaster Set pExtent = pPopGeoDataset.Extent vCell = (pRasterProp.MeanCellSize.X + pRasterProp.MeanCellSize.Y) / 2 ' Create a Rasterworkspace Dim pRasterWS As esriGeoDatabase.IWorkspace Set pRasterWS = SetRasterWorkspace(PathName) ' Create RasterAnalysis environment Dim pEnv As esriGeoAnalyst.IRasterAnalysisEnvironment Set pEnv = New esriGeoAnalyst.RasterAnalysis pEnv.SetCellSize esriGeoAnalyst.esriRasterEnvValue, vCell pEnv.SetExtent esriGeoAnalyst.esriRasterEnvValue, pExtent Set pEnv.OutWorkspace = pRasterWS ' Set to default so that it works for all the Ops in this session pEnv.SetAsNewDefaultEnvironment Set pConvOp = New esriGeoAnalyst.RasterConversionOp ' !!! MLH Maybe this line should go here. 'Convert the population enumeration districts to raster Dim pPopRasterDataset As esriGeoDatabase.IRasterDataset Set pPopRasterDataset = pConvOp.ToRasterDataset(pPopGeoDataset, "GRID", pRasterWS, "PopRaster") 'This section adds the rasterized population dataset to the map. 'It's not really a necessary step and could be omitted. Dim pPopRasterLayer As esriCarto.IRasterLayer Set pPopRasterLayer = New esriCarto.RasterLayer pPopRasterLayer.CreateFromDataset pPopRasterDataset pMXDoc.FocusMap.AddLayer pPopRasterLayer 'End of optional section __________________________________________________ Dim ConversionTime As Variant ConversionTime = Now 'Combine the rasterized population districts with the landcover raster Dim pPopRBCollection As esriDataSourcesRaster.IRasterBandCollection Set pPopRBCollection = pPopRasterLayer.Raster Dim pAncRBCollection As esriDataSourcesRaster.IRasterBandCollection Set pAncRBCollection = pRasterLayer.Raster Dim pPopRasterBand As esriDataSourcesRaster.IRasterBand Set pPopRasterBand = pPopRBCollection.Item(0) Dim pAncRasterBand As esriDataSourcesRaster.IRasterBand Set pAncRasterBand = pAncRBCollection.Item(0) ' Create a raster bandcollection Dim pRBCollection As esriDataSourcesRaster.IRasterBandCollection Set pRBCollection = New esriDataSourcesRaster.Raster pRBCollection.AppendBand pPopRasterBand pRBCollection.AppendBand pAncRasterBand Dim pInputDataset As esriGeoDatabase.IGeoDataset Set pInputDataset = pRBCollection ' Declare the output raster object Dim pCombDataset As esriGeoDatabase.IGeoDataset ' Create the RasterLocalOp object Dim pLocalOp As esriSpatialAnalyst.ILocalOp Set pLocalOp = New esriSpatialAnalyst.RasterLocalOp ' Calls the method Set pCombDataset = pLocalOp.Combine(pInputDataset) Dim pCombRastLyr As esriCarto.IRasterLayer Set pCombRastLyr = New esriCarto.RasterLayer With pCombRastLyr .CreateFromRaster pCombDataset .Name = "Dasymetric_result" End With pMXDoc.FocusMap.AddLayer pCombRastLyr Dim pCombDispTable As esriCarto.IDisplayTable Set pCombDispTable = pCombRastLyr Dim pCombTable As esriGeoDatabase.ITable Set pCombTable = pCombDispTable.DisplayTable 'Save the combined dataset Dim pDS As esriGeoDatabase.IDataset Dim pCombRast As esriGeoDatabase.IRaster Set pCombRast = pCombDataset Dim pCombRBColl As esriDataSourcesRaster.IRasterBandCollection Set pCombRBColl = pCombRast Set pDS = pCombRBColl.SaveAs("Dasy_Rast", pRasterWS, "GRID") 'Join the population table to the new raster output table ' ++ Create the MemoryRelationshipClass that defines what is to be joined Dim pMemRelClassFact As esriGeoDatabase.IMemoryRelationshipClassFactory Set pMemRelClassFact = New esriGeoDatabase.MemoryRelationshipClassFactory Dim pRelClass As esriGeoDatabase.IRelationshipClass Set pRelClass = pMemRelClassFact.Open("PopToRaster", pPopFeatureClass, "FID", pCombTable, "Popraster", _ "forward", "backward", esriGeoDatabase.esriRelCardinalityOneToMany) ' ++ Perform the join Dim pRelQueryTableFact As esriGeoDatabase.IRelQueryTableFactory Dim pRelQueryTab As esriGeoDatabase.ITable Set pRelQueryTableFact = New esriGeoDatabase.RelQueryTableFactory Set pCombTable = pRelQueryTableFact.Open(pRelClass, False, Nothing, Nothing, "", True, False) ' Add it to the relates for the feature layer in Map Dim pRelClassCollEdit As esriCarto.IRelationshipClassCollectionEdit Set pRelClassCollEdit = pCombRastLyr pRelClassCollEdit.AddRelationshipClass pRelClass 'Display the join Dim pDispRC As esriCarto.IDisplayRelationshipClass Set pDispRC = pCombRastLyr pDispRC.DisplayRelationshipClass pRelClass, esriGeoDatabase.esriLeftInnerJoin 'Export the resulting table to a dbf file Set pDS = Nothing Set pDS = pCombTable Dim pDSName As esriGeoDatabase.IDatasetName Set pDSName = pDS.FullName Dim pOutDSName As esriGeoDatabase.IDatasetName Set pOutDSName = New esriGeoDatabase.TableName pOutDSName.Name = "DasyTable" Dim pWkSpFactory As esriGeoDatabase.IWorkspaceFactory Set pWkSpFactory = New esriDataSourcesFile.ShapefileWorkspaceFactory Dim pWkSp As esriGeoDatabase.IWorkspace Set pWkSp = pWkSpFactory.OpenFromFile(PathName & "\", 0) Dim pWkSpDS As esriGeoDatabase.IDataset Set pWkSpDS = pWkSp Dim pWkSpName As esriGeoDatabase.IWorkspaceName Set pWkSpName = pWkSpDS.FullName Set pOutDSName.WorkspaceName = pWkSpName ' Export (Selection is ignored) Dim pExpOp As esriGeoDatabaseUI.IExportOperation Set pExpOp = New esriGeoDatabaseUI.ExportOperation pExpOp.ExportTable pDSName, Nothing, Nothing, pOutDSName, Application.hWnd ' add the table to map Dim pName As esriSystem.IName Dim pDasyTable As esriGeoDatabase.ITable Dim pStTab As esriCarto.IStandaloneTable Dim pStTabColl As esriCarto.IStandaloneTableCollection Set pName = pOutDSName Set pDasyTable = pName.Open Set pStTab = New esriCarto.StandaloneTable Set pStTab.Table = pDasyTable Set pStTabColl = pMXDoc.FocusMap pStTabColl.AddStandaloneTable pStTab pMXDoc.UpdateContents Dim IntersectTime As Variant IntersectTime = Now 'Create new fields CreateFields pDasyTable pErrorTrack = "Pre-Calculations" 'Collect unique tract ids Dim pPopIDs As New Collection Dim pQueryfilter As esriGeoDatabase.IQueryFilter Set pQueryfilter = Nothing Dim pPopIDField As Variant pPopIDField = "PopRaster" Set pPopIDs = GetUnique(pQueryfilter, pDasyTable, pPopIDField) 'Collect unique ancillary values Dim pAncIDs As New Collection Set pQueryfilter = Nothing Dim pAncIDField As Variant pAncIDField = VBA.Left(pAncLayer.Name, 10) Set pAncIDs = GetUnique(pQueryfilter, pDasyTable, pAncIDField) 'Need to start an edit session to update the table Dim pEditor As esriEditor.IEditor Dim pID As New esriSystem.UID pID = "esriCore.Editor" Set pEditor = Application.FindExtensionByCLSID(pID) pEditor.StartEditing pWkSp 'Perform pre-selection calculations pEditor.StartOperation PreCalc pPopIDs, PresetClasses, PresetDensities, pPopIDField, pAncIDField, pDasyTable, popFieldName ' !!! MLH: Added popFieldName to argument list pEditor.StopOperation "Pre-Calculations" Dim PreCalcTime As Variant PreCalcTime = Now pErrorTrack = "Selection" 'Select representative tracts, probably by percent method comparing the count column with the populated area column 'Don't need to perform any special selection, since it can all be done with a simple queryfilter. 'For each ancillary class, find rows where Count > Percent * INHAB_CNT 'Select the rows and calculate density statistics on them. 'Calculate representative density by dividing total representative population by total representative area. Save stats for end. 'Calculate densities for representative tracts, i.e. total pop/poparea for statsfile purposes? Dim pStats As esriGeoDatabase.IDataStatistics Dim pResults As esriSystem.IStatisticsResults Dim pCursor As esriGeoDatabase.ICursor Dim pCalc As esriGeoDatabaseUI.ICalculator Dim pPDArray As New Collection Dim pBegCount As New Collection Dim pBegMean As New Collection Dim pBegMin As New Collection Dim pBegMax As New Collection Dim pBegStdDev As New Collection Dim pTotAreaArray As New Collection Dim pTotPopArray As New Collection Dim pFlagClass As New Collection Dim pPopArray As New Collection Dim pCalcMethod As New Collection Dim pUnsampled As New Collection Dim pSampledArea As Double Dim pTotArea As Double Dim pTotPop As Double Dim pClass As Variant For Each pClass In pAncIDs Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = pAncIDField & " = " & pClass & " AND Count >= " & Percent & " * INHAB_CNT" 'Now calculate the statistics for the selected tracts Set pCursor = pDasyTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "INHAB_CNT" 'Want to be sure to only include populated area Set pResults = pStats.Statistics pTotArea = pResults.Sum pTotAreaArray.Add pTotArea, pClass pSampledArea = pSampledArea + pTotArea Set pCursor = Nothing Set pStats = Nothing Set pResults = Nothing Set pCursor = pDasyTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = popFieldName Set pResults = pStats.Statistics pTotPop = pResults.Sum pTotPopArray.Add pTotPop, pClass 'Now collect the density statistics for analysis Set pCursor = pDasyTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "INHAB_DENS" Set pResults = pStats.Statistics Dim BegCount As Double BegCount = pResults.Count pBegCount.Add BegCount, pClass Dim BegMean As Double BegMean = pResults.Mean pBegMean.Add BegMean, pClass Dim BegMin As Double BegMin = pResults.Minimum If BegCount = 0 Then BegMin = 0 pBegMin.Add BegMin, pClass Dim BegMax As Double BegMax = pResults.Maximum If BegCount = 0 Then BegMax = 0 pBegMax.Add BegMax, pClass Dim BegStdDev As Double BegStdDev = pResults.StandardDeviation pBegStdDev.Add BegStdDev, pClass Dim pNewDensity As Double If pDasyTable.RowCount(pQueryfilter) > 0 And pTotArea > 0 Then 'To avoid errors if there are no tract polygons selected pNewDensity = pTotPop / pTotArea Else: pNewDensity = 0 End If 'If this is one of our preset classes, flag it through the unsampled classes selection Dim pPresetC As Variant For Each pPresetC In PresetClasses If pClass = pPresetC Then pNewDensity = -1 'To exclude from our insufficient sample code pCalcMethod.Add "Preset", pClass 'Mark class as preset Exit For End If Next 'Flag classes and regions that were insufficiently sampled. If BegCount < 2 And pNewDensity <> -1 Then pNewDensity = 0 'Set to zero so it doesn't contribute to the total pUnsampled.Add pClass 'Place in array for unsampled processing. pCalcMethod.Add "Areal Weight", pClass 'Mark class as having used the areal weight method ElseIf BegCount > 1 And pNewDensity <> -1 Then pCalcMethod.Add "Sampled", pClass End If If pNewDensity = -1 Then pNewDensity = PresetDensities(pClass) 'Remove flag, set to preset value. Dim pPDTotal As Double 'put that landcover population density value into an array pPDArray, pPDArray.Add pNewDensity, pClass 'sum all population density values into the total sum density pPDTotal pPDTotal = pPDTotal + pNewDensity 'Calculate a population estimate for all dasymetric polys with this class by multiplying the class density by the poly area If pNewDensity <> 0 Then Set pCalc = New esriGeoDatabaseUI.Calculator Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = pAncIDField & " = " & pClass Set pCursor = pDasyTable.Update(pQueryfilter, False) With pCalc Set .Cursor = pCursor .Expression = "[Count] * " & pNewDensity .Field = "POPEST" End With pCalc.Calculate Set pCalc = Nothing End If Next 'next ancillary class Dim SelectionTime As Variant SelectionTime = Now 'Handle unsampled classes pErrorTrack = "initial population estimation" 'Method for estimating unsampled ancillary class densities Dim pEstPop, pExtraPop As Double pEditor.StartOperation PopulationEst pPopIDs, pDasyTable, popFieldName, "Popraster", pErrorTrack pEditor.StopOperation "Population Estimation" 'Now calculate the population densities of the unsampled classes based on these estimates Dim pEstArea As Double For Each pClass In pUnsampled Set pCursor = Nothing Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = pAncIDField & " = " & pClass Set pCursor = pDasyTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "POPEST" Set pResults = pStats.Statistics pEstPop = pResults.Sum Set pCursor = pDasyTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "Count" Set pResults = pStats.Statistics pEstArea = pResults.Sum If pEstArea > 0 Then 'Because dividing by zero is bad, although this should never occur. pNewDensity = pEstPop / pEstArea 'put the new landcover population density value into pPDArray, pPDArray.Remove pClass pPDArray.Add pNewDensity, pClass 'add this new density into the total sum density pPDTotal pPDTotal = pPDTotal + pNewDensity End If 'Put class density into a column pEditor.StartOperation Set pCalc = New esriGeoDatabaseUI.Calculator pNewDensity = pPDArray(pClass) 'Add this value to the "CLSDENSITY" field for every row with that landcover, Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = pAncIDField & " = " & pClass Set pCursor = pDasyTable.Update(pQueryfilter, False) With pCalc Set .Cursor = pCursor .Expression = "[Count] * " & pNewDensity .Field = "POPEST" End With pCalc.Calculate Set pCalc = Nothing pEditor.StopOperation "Re-estimate Population" Next 'pClass in pUnsampled Dim SAMTime As Variant SAMTime = Now 'Final Calculations are standardized, although there may be two options. pErrorTrack = "final calculations" Dim pRegQueryfilter As New esriGeoDatabase.QueryFilter pEditor.StartOperation FinalCalc pDasyTable, pRegQueryfilter, pPopIDs, "Popraster", popFieldName pEditor.StopOperation "Final Calculations" 'All done with editing tables. pEditor.StopEditing (True) Dim FinalCalcTime As Variant FinalCalcTime = Now 'Now generate a csv file with summary statistics for each ancillary class pErrorTrack = "stat file creation" Dim pStatCount, pMean, pMin, pMax, pStdDev As Variant Dim BegTotPop, BegTotArea, BegPopPerc, BegAreaPerc, PercSamArea As Variant Dim pCatArea, pCatPercArea, pCatPop, pCatPercPop As Variant Dim FinTotPop, FinTotArea As Variant Dim pSummary As String 'Calculate the Original and final total population for comparison purposes. Set pCursor = pPopFeatureLayer.Search(Nothing, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = popFieldName Set pResults = pStats.Statistics BegTotPop = pResults.Sum Set pCursor = pPopRasterBand.AttributeTable.Search(Nothing, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "Count" Set pResults = pStats.Statistics BegTotArea = pResults.Sum Set pCursor = pDasyTable.Search(Nothing, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "NEWPOP" Set pResults = pStats.Statistics FinTotPop = pResults.Sum Set pCursor = pDasyTable.Search(Nothing, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "TRCTCOUNT" Set pResults = pStats.Statistics FinTotArea = pResults.Sum Dim output As Object Dim pSuffix As Variant Dim outtext As Object Dim pAncArray As New Collection Set output = CreateObject("Scripting.FileSystemObject") Set outtext = output.CreateTextFile(PathName & "\Dasymetric_Stats" & pSuffix & ".csv", True) pSummary = "Ancillary Class,Rep Sample Count,Rep % Area,% Sampled Area,Rep % Pop,Rep Mean,Rep Min,Rep Max,Rep StdDev,Est Density," & _ "Sample Count,% Total Area,% Total Pop,Mean Density,Min Density,Max Density,StdDev,Calc Method" outtext.writeline (pSummary) Set pQueryfilter = New esriGeoDatabase.QueryFilter Set pAncArray = GetUnique(pQueryfilter, pDasyTable, pAncIDField) For Each pClass In pAncArray Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = pAncIDField & "= " & pClass Set pCursor = pDasyTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "NEWDENSITY" Set pResults = pStats.Statistics pStatCount = pResults.Count pMean = pResults.Mean pMin = pResults.Minimum pMax = pResults.Maximum pStdDev = pResults.StandardDeviation Set pCursor = pDasyTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "TRCTCOUNT" Set pResults = pStats.Statistics pCatArea = pResults.Sum pCatPercArea = pCatArea / FinTotArea Set pCursor = pDasyTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "NEWPOP" Set pResults = pStats.Statistics pCatPop = pResults.Sum pCatPercPop = pCatPop / FinTotPop BegPopPerc = pTotPopArray(pClass) / BegTotPop BegAreaPerc = pTotAreaArray(pClass) / BegTotArea If pSampledArea <> 0 Then PercSamArea = pTotAreaArray(pClass) / pSampledArea Else PercSamArea = 0 End If pSummary = pClass & ", " & pBegCount(pClass) & ", " & BegAreaPerc & ", " & PercSamArea & ", " & BegPopPerc & ", " & pBegMean(pClass) & ", " & pBegMin(pClass) & ", " & pBegMax(pClass) & ", " & pBegStdDev(pClass) & ", " & pPDArray(pClass) & ", " & _ pStatCount & ", " & pCatPercArea & ", " & pCatPercPop & ", " & pMean & ", " & pMin & ", " & pMax & ", " & pStdDev & ", " & pCalcMethod(pClass) outtext.writeline (pSummary) Next 'pClass in pAncArray Dim SelectType As String Dim Prompt As String outtext.writeline (SelectType) EndTime = Now Elapsed = DateDiff("s", StartTime, EndTime) Prompt = "The program took " & Elapsed & " seconds to run." outtext.writeline (Prompt) Elapsed = DateDiff("s", StartTime, ConversionTime) Prompt = "The program took " & Elapsed & " seconds to perform the vector to raster conversion." outtext.writeline (Prompt) Elapsed = DateDiff("s", ConversionTime, IntersectTime) Prompt = "The program took " & Elapsed & " seconds to perform the intersection." outtext.writeline (Prompt) Elapsed = DateDiff("s", IntersectTime, PreCalcTime) Prompt = "The program then took " & Elapsed & " seconds to perform the precalculations." outtext.writeline (Prompt) Elapsed = DateDiff("s", PreCalcTime, SelectionTime) Prompt = "The program then took " & Elapsed & " seconds to perform all selections and class calculations." outtext.writeline (Prompt) Elapsed = DateDiff("s", SelectionTime, SAMTime) Prompt = "The program then took " & Elapsed & " seconds to perform the smart areal weighting." outtext.writeline (Prompt) Elapsed = DateDiff("s", SAMTime, FinalCalcTime) Prompt = "The program then took " & Elapsed & " seconds to perform the final calculations." outtext.writeline (Prompt) Elapsed = DateDiff("s", FinalCalcTime, EndTime) Prompt = "The program then took " & Elapsed & " seconds to generate the summary table." outtext.writeline (Prompt) outtext.Close Set pPDArray = Nothing Set pAncArray = Nothing Set pBegCount = Nothing Set pBegMean = Nothing Set pBegMin = Nothing Set pBegMax = Nothing Set pBegStdDev = Nothing Set pTotAreaArray = Nothing Set pTotPopArray = Nothing Set pCalcMethod = Nothing Set pUnsampled = Nothing Set pCursor = Nothing Set pMXDoc = Nothing Set pCalc = Nothing Set pQueryfilter = Nothing Set pEditor = Nothing Set pRasterWS = Nothing Set pEnv = Nothing Set pConvOp = Nothing Set pRBCollection = Nothing Set pLocalOp = Nothing pRelClassCollEdit.RemoveAllRelationshipClasses Exit Sub 'Handle regions... 'Saving the code below, not sure when or whether we'll use it at some point. 'Remove the old join, join the new final table back to the raster table ' Join the ancillary table - this might have to be done with standalone tables ' Dim pAncTable As ITable ' Set pAncTable = pRasterLayer ' Set pMemRelClassFact = New MemoryRelationshipClassFactory ' Dim pDasyRelClass As IRelationshipClass ' Set pDasyRelClass = pMemRelClassFact.Open("DasyTable", pAncTable, "Value", pCombTable, "LC3RASTERd", _ ' "forward", "backward", esriRelCardinalityOneToMany) ' Dim pDasyTable As ITable ' Set pRelQueryTableFact = New RelQueryTableFactory ' Set pDasyTable = pRelQueryTableFact.Open(pDasyRelClass, True, Nothing, Nothing, "", True, True) '' Add it to the relates for the feature layer in Map ' pRelClassCollEdit.AddRelationshipClass pDasyRelClass ' 'Display the join ' pDispRC.DisplayRelationshipClass pDasyRelClass, esriLeftInnerJoin Exit Sub ERH: MsgBox Err.Description & ", Error Number " & Err.Number MsgBox pErrorTrack Set pRasterWS = Nothing Set pEnv = Nothing Set pConvOp = Nothing Set pRBCollection = Nothing Set pLocalOp = Nothing pRelClassCollEdit.RemoveAllRelationshipClasses If pEditor.EditState = esriEditor.esriStateEditing Then pEditor.StopEditing True End Sub Private Function SetRasterWorkspace(ByVal PathName As String) As esriGeoDatabase.IWorkspace ' Given a pathname, returns the raster workspace object for that path On Error GoTo ERH Dim pWSF As esriGeoDatabase.IWorkspaceFactory Set pWSF = New esriDataSourcesRaster.RasterWorkspaceFactory Dim pWS As esriGeoDatabase.IWorkspace Set pWS = pWSF.OpenFromFile(PathName, 0) Set SetRasterWorkspace = pWS Exit Function ERH: Set SetRasterWorkspace = Nothing End Function 'Create all of the new fields in the output table, and input table if necessary Private Sub CreateFields(pDasyTable As esriGeoDatabase.ITable) Dim pField As esriGeoDatabase.IField Dim pFieldEdit As esriGeoDatabase.IFieldEdit Dim pName As Variant Dim pFieldNames As New Collection Set pField = New esriGeoDatabase.Field Set pFieldEdit = pField pFieldNames.Add "TRCTCOUNT" ' If pDasyTable.FindField("POPAREA") = -1 Then pFieldNames.Add "POPAREA" 'Add only if it didn't exist already pFieldNames.Add "INHAB" pFieldNames.Add "INHAB_CNT" pFieldNames.Add "INHAB_DENS" pFieldNames.Add "POPEST" pFieldNames.Add "TOTALFRACT" pFieldNames.Add "NEWPOP" pFieldNames.Add "NEWDENSITY" For Each pName In pFieldNames With pFieldEdit .Editable = True .Name = pName .Type = esriGeoDatabase.esriFieldTypeDouble End With pDasyTable.AddField pField Next 'pName in pFieldNames End Sub 'Get the unique values in the for the Layer, Field, and QueryFilter and put them in an array Private Function GetUnique(pQueryfilter As esriGeoDatabase.IQueryFilter, pTable As esriGeoDatabase.ITable, pFieldName As Variant) As Collection Dim pCursor As esriGeoDatabase.ICursor Dim pData As esriGeoDatabase.IDataStatistics Dim pEnumvarP As esriSystem.IEnumVariantSimple Set GetUnique = New Collection Set pCursor = pTable.Search(pQueryfilter, False) Set pData = New esriGeoDatabase.DataStatistics pData.Field = pFieldName Set pData.Cursor = pCursor Set pEnumvarP = pData.UniqueValues Dim pPop As Variant pPop = pEnumvarP.Next Dim i As Long For i = 1 To pData.UniqueValueCount GetUnique.Add pPop pPop = pEnumvarP.Next Next i Set pCursor = Nothing Set pData = Nothing End Function 'Perform a number of calculations prior to the selection process Private Sub PreCalc(pPopArray, PresetClasses As Collection, PresetDensities As Collection, PopKeyName, AncCatName As Variant, _ pDasyTable As esriGeoDatabase.ITable, popFieldName As Variant) ' !!! MLH: Added popFieldName as an argument 'Populate binary inhabited/noninhabited column. 'Make sure we have a total pixel count for each tract (carryover from rasterized tracts?) 'Calculate the inhabited pixel count for each tract. 'All of the above calculations can probably be considered precalculations, and done in one full pass through the tracts. Dim pCursor As esriGeoDatabase.ICursor Dim pCalc As esriGeoDatabaseUI.ICalculator Dim pSearchFilter As esriGeoDatabase.IQueryFilter Dim pUpdateFilter As esriGeoDatabase.IQueryFilter Dim pStats As esriGeoDatabase.IDataStatistics Dim pResults As esriSystem.IStatisticsResults Dim pPresetC As Variant Dim pClassNum As Variant Dim pPop As Variant 'Populate column designating inhabited ancillary classes from presets. Set pCalc = New esriGeoDatabaseUI.Calculator Set pCursor = pDasyTable.Update(Nothing, False) With pCalc Set .Cursor = pCursor .Expression = 1 .Field = "INHAB" End With pCalc.Calculate Set pCalc = Nothing Dim i As Integer i = 1 For Each pPresetC In PresetClasses Set pSearchFilter = New esriGeoDatabase.QueryFilter pSearchFilter.WhereClause = AncCatName & " = " & pPresetC If pDasyTable.RowCount(pSearchFilter) = 0 Then PresetClasses.Remove (i) 'Program tends to crash if a preset class is not found in the table. Remove from array to prevent this. Else 'If the class does exist... If PresetDensities(pPresetC) = 0 Then 'Only if the class has been preset to zero Set pUpdateFilter = New esriGeoDatabase.QueryFilter pUpdateFilter.WhereClause = AncCatName & " = " & pPresetC Set pCursor = pDasyTable.Update(pUpdateFilter, False) Set pCalc = New esriGeoDatabaseUI.Calculator With pCalc Set .Cursor = pCursor .Expression = 0 .Field = "INHAB" End With pCalc.Calculate Set pCalc = Nothing End If End If i = i + 1 Next 'pPresetC In PresetClasses For Each pPop In pPopArray 'First populate the TRCTCOUNT field by summing the COUNT field Set pSearchFilter = New esriGeoDatabase.QueryFilter pSearchFilter.WhereClause = PopKeyName & " = " & pPop Set pCursor = pDasyTable.Search(pSearchFilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "Count" Set pResults = pStats.Statistics Set pCalc = New esriGeoDatabaseUI.Calculator Set pUpdateFilter = New esriGeoDatabase.QueryFilter pUpdateFilter.WhereClause = PopKeyName & " = " & pPop Set pCursor = pDasyTable.Update(pUpdateFilter, False) With pCalc Set .Cursor = pCursor .Expression = pResults.Sum .Field = "TRCTCOUNT" End With pCalc.Calculate 'Clear variables before moving on Set pSearchFilter = Nothing Set pCursor = Nothing Set pStats = Nothing Set pResults = Nothing Set pCalc = Nothing Set pUpdateFilter = Nothing 'Calculate the total populated pixel count of each population unit and fill the appropriate columns Set pSearchFilter = New esriGeoDatabase.QueryFilter pSearchFilter.WhereClause = PopKeyName & " = " & pPop & " AND INHAB = 1" Set pCursor = pDasyTable.Search(pSearchFilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "Count" Set pResults = pStats.Statistics Set pCalc = New esriGeoDatabaseUI.Calculator Set pUpdateFilter = New esriGeoDatabase.QueryFilter pUpdateFilter.WhereClause = PopKeyName & " = " & pPop & " AND INHAB = 1" If pDasyTable.RowCount(pUpdateFilter) <> 0 Then 'Not happy if population polys ended up clipped. Set pCursor = pDasyTable.Update(pUpdateFilter, False) With pCalc Set .Cursor = pCursor .Expression = pResults.Sum .Field = "INHAB_CNT" End With pCalc.Calculate End If Set pCursor = Nothing Set pStats = Nothing Set pResults = Nothing Set pCalc = Nothing Set pSearchFilter = Nothing Set pUpdateFilter = Nothing Next 'pPop in pPopArray 'Calculate the population density of the inhabited area (POPFIELDNAME / INHABCNT) in people/pixel, purely for comparison Set pCalc = New esriGeoDatabaseUI.Calculator Set pCursor = pDasyTable.Update(Nothing, False) With pCalc Set .Cursor = pCursor .Expression = "[" + popFieldName + "] / [TRCTCOUNT]" '!!! MLH: Replaced hard-coded TOTPOP field name with popFieldName .Field = "INHAB_DENS" End With pCalc.Calculate 'Clear variables again before exiting sub Set pCursor = Nothing Set pCalc = Nothing End Sub 'Subroutine for estimating population of unsampled classes based on existing data Private Sub PopulationEst(pPopArray, pTable As esriGeoDatabase.ITable, popFieldName, PopKeyName As Variant, pErrorTrack As Variant) Dim pCursor As esriGeoDatabase.ICursor Dim pCalc As esriGeoDatabaseUI.ICalculator Dim pStats As esriGeoDatabase.IDataStatistics Dim pResults As esriSystem.IStatisticsResults Dim pQueryfilter As esriGeoDatabase.IQueryFilter Dim pEstPop, pTotalPop, pExtraPop, pEstArea As Double Dim pIndex As Long Dim pPop As Variant Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = "POPEST = 0 AND INHAB = 1" If pTable.RowCount(pQueryfilter) <> 0 Then 'Skip the entire operation if there are no empty rows. For Each pPop In pPopArray Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = PopKeyName & " = " & pPop & " and POPEST = 0 AND INHAB = 1" If pTable.RowCount(pQueryfilter) <> 0 Then 'Skip this operation if there are no empty rows in this source polygon. Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = PopKeyName & " = " & pPop pQueryfilter.SubFields = "POPEST" Set pCursor = pTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "POPEST" Set pResults = pStats.Statistics pEstPop = pResults.Sum Set pQueryfilter = New esriGeoDatabase.QueryFilter Set pCursor = Nothing Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = PopKeyName & " = " & pPop pQueryfilter.SubFields = popFieldName Set pCursor = pTable.Search(pQueryfilter, False) pIndex = pCursor.FindField(popFieldName) pTotalPop = pCursor.NextRow.Value(pIndex) If pEstPop < pTotalPop Then 'Otherwise there's no remaining population to distribute and we can leave POPEST=0 Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = PopKeyName & " = " & pPop & " and POPEST <> 0" Set pCursor = pTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "Count" Set pResults = pStats.Statistics pEstArea = pResults.Sum pExtraPop = pTotalPop - pEstPop Set pCursor = Nothing Set pQueryfilter = Nothing Set pCalc = Nothing Set pCalc = New esriGeoDatabaseUI.Calculator Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = PopKeyName & " = " & pPop & " and POPEST = 0" Set pCursor = pTable.Update(pQueryfilter, False) 'For this calculation we want to distribute the extra population amongst the unsampled inhabited dasymetric polygons 'using areal weighting = [NEWAREA] / pExtraArea. pExtraArea = [POPAREA] - pEstArea With pCalc Set .Cursor = pCursor .PreExpression = "Dim pExtraArea" & vbNewLine & _ "Dim EstPop" & vbNewLine & _ "pExtraArea = [INHAB_CNT] - " & pEstArea & vbNewLine & _ "If pExtraArea > 0 and [INHAB] = 1 Then" & vbNewLine & _ "EstPop = ([Count] / pExtraArea) * " & pExtraPop & vbNewLine & _ "Else EstPop = 0" & vbNewLine & _ "End If" .Expression = "EstPop" .Field = "POPEST" End With pErrorTrack = "The program fails on Tract " & pPop & " with an estimated population of " & pEstPop & _ " an actual population of " & pTotalPop & " and an estimated area of " & pEstArea pCalc.Calculate End If End If Next 'pPop in pPopArray End If Set pCalc = Nothing Set pCursor = Nothing Set pQueryfilter = Nothing Set pStats = Nothing Set pResults = Nothing End Sub 'Final calculations Private Sub FinalCalc(pDasyTable As esriGeoDatabase.ITable, pRegQueryfilter As esriGeoDatabase.IQueryFilter, pPopArray As Collection, _ PopKeyName, popFieldName As Variant) Dim pCalc As esriGeoDatabaseUI.ICalculator Dim pCursor As esriGeoDatabase.ICursor Dim pStats As esriGeoDatabase.IDataStatistics Dim pResults As esriSystem.IStatisticsResults Dim pQueryfilter As esriGeoDatabase.IQueryFilter Dim pPop As Variant Dim pPDFARsum As Double 'Find the sum of all the PDF_AR values for each Population District and store as pPDFARsum 'Calculate the total fraction as [PDF_AR] / pPDFARsum For Each pPop In pPopArray Set pQueryfilter = New esriGeoDatabase.QueryFilter pQueryfilter.WhereClause = PopKeyName & " = " & pPop Set pCursor = pDasyTable.Search(pQueryfilter, False) Set pStats = New esriGeoDatabase.DataStatistics Set pStats.Cursor = pCursor pStats.Field = "POPEST" Set pResults = pStats.Statistics pPDFARsum = pResults.Sum If pPDFARsum > 0 Then 'Because dividing by zero is bad. Set pCalc = New esriGeoDatabaseUI.Calculator Set pCursor = pDasyTable.Update(pQueryfilter, False) With pCalc Set .Cursor = pCursor .Expression = "[POPEST] / " & pPDFARsum .Field = "TOTALFRACT" End With pCalc.Calculate Set pCalc = Nothing End If Next 'Calculate the actual population of the new, intersected polygon by multiplying 'the total fraction times the population of that tract Set pCalc = New esriGeoDatabaseUI.Calculator Set pCursor = pDasyTable.Update(pRegQueryfilter, False) With pCalc Set .Cursor = pCursor .Expression = "[TOTALFRACT] * [" & popFieldName & "]" .Field = "NEWPOP" End With pCalc.Calculate Set pCalc = Nothing 'Lastly calculate a density column for statistical purposes. Set pCalc = New esriGeoDatabaseUI.Calculator Set pCursor = pDasyTable.Update(pRegQueryfilter, False) With pCalc Set .Cursor = pCursor .Expression = "[NEWPOP] / [Count]" .Field = "NEWDENSITY" End With pCalc.Calculate Set pCalc = Nothing Set pCursor = Nothing End Sub Private Sub UIButtonControl1_Click() Dasymetric_Raster End Sub