Home arrow GIS Data & Resources arrow Scripts and Code arrow Visual Basic / VBA arrow SL Solar Raster Analysis: Working with Large Rasters
SL Solar Raster Analysis: Working with Large Rasters PDF Print E-mail

Written by Bert Granberg,

Occasionally in the IT and GIS worlds we are faced with conceptually simple tasks that need to be performed at a scale in which the 'out of the box' toolsets don't execute well. That's not to say that most of the GIS desktop tools are bad (they're generally superb), but rather, if the nature of your project is very CPU or RAM intensive you may want or need to design the process to specifically optimize memory and/or CPU usage.

Here is one such problem and its solution.

Sunlit duration derived from 1 meter LIDAR data, Salt Lake City/County Building Area, July

Problem:

For Salt Lake City's Solar Cities USA project, 1 meter resolution LIDAR data was used in conjunction with ESRI's very cool Solar Analysis toolkit to generate a raster analysis grid for total sunlit hours and total solar radiation for the entire city, for each month of the year. A sunlit grid example for a 3 block area centered on the Salt Lake City/County building is shown above.

In all we're talking about 24 grids, each with approximately 500 million cells. That's 12 billion potential data points and just how often do you get to use 'billion' in GIS work (welcome to the world of LIDAR!). Generating these datasets took weeks and was only possible due to the clever Python work by Kevin Bell that allowed for the data to be built in smaller tiles that considered the neighboring tiles (to account for nearby shadow casting features).

The next problem to solve was how to store this data so that it could be quickly queried by a web service to provide information about solar resources on properties and structures throughout the city. After long investigation, AGRC has concluded that no one is going to wait around for the results of 24 raster grid queries...its just too slow. The old proverb of 'raster is faster' seems to apply much more to map algebra type overlay operations than to 'finding a needle in a haystack' (which is what getting the solar profile for a residential rooftop from these 24 large datasets is all about).

Solution:

The proposed solution is to convert the raster data to a single file geodatabase point-based feature class with 24 attributes describing the monthly sunlit and solar radiation values. The sunlit dataset will give users a good idea of the solar resources for rack mounted PV & water heat systems, landscaping etc. The solar radition values will give users an idea of the solar energy potential for existing surfaces (flush mount PV, passive cooling strategy, etc. The output point dataset containing all data from the 24 raster grids will have to be in a FGDB storage format to allow for large file sizes and not rack up big network storage costs.

The final problem is how to do this. Again, out of the box tools failed due to wait time and memory shortages. The solution seems to be working with the ArcObjects IRasterCursor object and iterating through the cells of all 24 rasters reading in only 128 rows of the 24 rasters into memory at a time. This is precisely what IRasterCursor does (see code below). The product, when finished will only contain points that have data associated with them and should be around 250 million points. It look like it'll take about 24 hours to run and seems to run in fairly linear time relative to the total number of grid cells.

Stay tuned for more on this sometimes bizarre undertaking.

Update 8/10/09: The dataset is done processing and seems to be working albeit a little slowly. To address this I am recalculating the spatial grid size to 30 meters. ArcCatalog suggested 1.4 meters (which would give 9 points per grid cell) but this seems like it would be too many grid cells (both for calculation time for building the index and also for processing a typical residential or commercial area query). I am calculating the spatial index, originally 3000 meters, to 30 meters

Update 8/11/09:  Processing is done, took less than 24 hours on our enterprise grade windows box (8 GB of RAM). Final product is 56 GB point feature class in a file-geodatabase containing 260 million points, each with 24 floating point attributes. Spatial queries of the size we are interested in (approximately 1000 sq meters) take less than 1 second in ArcMap. Looks like I might have guessed right on the 30 meter spatial index.

Update 8/17/09:  We decided to convert the fields storing the solar duration and radiation from doubles to long integers to see if it saved time and storage resources. Redefined an empty feature class and ran the process over the weekend (it only takes about 10 hours now). The resulting file geodatabase is now only 36 GB and the query time is about one third of what it was. Approximate query times:

  • A 3 acre query (about the size of the Delta Center roof in Salt Lake): 18 seconds.
  • The Governor's mansion rooftop: 4 seconds
  • A tennis court: 1.5 seconds
  • A TRAX light rail platform shelter: 0.8 seconds

Test it yourself:  http://dagrc.utah.gov/solar (note: we only have data for Salt Lake City)

Output sample, July sunlit duration, Salt Lake Main Library Building.

One meter point of July sunlit duration, snapshot of Salt Lake Main Library.

Output sample, Identify results for highlighted red pixel near northwest corner of Salt Lake Main Library building. Shows SOLar radiation in watt hours and sunlit DURation in hours for each month (1-12).

Identify results for red highlighted point in northwest corner of library image

Notes:

Three important considerations for evaluating the LIDAR based solar modelling results:

  1. LIDAR data is from 2006
  2. All trees are modeled based on 1st return LIDAR (as if they were wrapped by a blanket or were conifers)
  3. The shadows cast by the Wasatch and Oquirrh mountains were not considered in this analysis

 

Public Sub monthlySolarRastersToFGDBPoints()

    Dim pMxDoc As IMxDocument
    Dim pMap As IMap
    
    Dim pFLayer As IFeatureLayer
    Dim pFC As IFeatureClass
    Dim pFCursor As IFeatureCursor
    Dim pFeatureBuffer As IFeatureBuffer
    
    Dim pRasterLayer As IRasterLayer
    Dim pRaster As IRaster
    
    Dim pRasterCursorDur1 As IRasterCursor
    Dim pRasterCursorDur2 As IRasterCursor
    Dim pRasterCursorDur3 As IRasterCursor
    Dim pRasterCursorDur4 As IRasterCursor
    Dim pRasterCursorDur5 As IRasterCursor
    Dim pRasterCursorDur6 As IRasterCursor
    Dim pRasterCursorDur7 As IRasterCursor
    Dim pRasterCursorDur8 As IRasterCursor
    Dim pRasterCursorDur9 As IRasterCursor
    Dim pRasterCursorDur10 As IRasterCursor
    Dim pRasterCursorDur11 As IRasterCursor
    Dim pRasterCursorDur12 As IRasterCursor
    Dim pRasterCursorSol1 As IRasterCursor
    Dim pRasterCursorSol2 As IRasterCursor
    Dim pRasterCursorSol3 As IRasterCursor
    Dim pRasterCursorSol4 As IRasterCursor
    Dim pRasterCursorSol5 As IRasterCursor
    Dim pRasterCursorSol6 As IRasterCursor
    Dim pRasterCursorSol7 As IRasterCursor
    Dim pRasterCursorSol8 As IRasterCursor
    Dim pRasterCursorSol9 As IRasterCursor
    Dim pRasterCursorSol10 As IRasterCursor
    Dim pRasterCursorSol11 As IRasterCursor
    Dim pRasterCursorSol12 As IRasterCursor
    Dim pPixelBlockDur1 As IPixelBlock
    Dim pPixelBlockDur2 As IPixelBlock
    Dim pPixelBlockDur3 As IPixelBlock
    Dim pPixelBlockDur4 As IPixelBlock
    Dim pPixelBlockDur5 As IPixelBlock
    Dim pPixelBlockDur6 As IPixelBlock
    Dim pPixelBlockDur7 As IPixelBlock
    Dim pPixelBlockDur8 As IPixelBlock
    Dim pPixelBlockDur9 As IPixelBlock
    Dim pPixelBlockDur10 As IPixelBlock
    Dim pPixelBlockDur11 As IPixelBlock
    Dim pPixelBlockDur12 As IPixelBlock
    Dim pPixelBlockSol1 As IPixelBlock
    Dim pPixelBlockSol2 As IPixelBlock
    Dim pPixelBlockSol3 As IPixelBlock
    Dim pPixelBlockSol4 As IPixelBlock
    Dim pPixelBlockSol5 As IPixelBlock
    Dim pPixelBlockSol6 As IPixelBlock
    Dim pPixelBlockSol7 As IPixelBlock
    Dim pPixelBlockSol8 As IPixelBlock
    Dim pPixelBlockSol9 As IPixelBlock
    Dim pPixelBlockSol10 As IPixelBlock
    Dim pPixelBlockSol11 As IPixelBlock
    Dim pPixelBlockSol12 As IPixelBlock
    
    Dim x, y, startX, startY, currX, currY, increment, pbCount, flushCount As Long
    Dim pPoint As IPoint
    
    Set pMxDoc = ThisDocument
    Set pMap = pMxDoc.FocusMap
    
    Set pFLayer = pMap.Layer(0)
    Set pFC = pFLayer.FeatureClass
    Set pFCursor = pFC.Insert(True)
    
    Set pRasterLayer = pMap.Layer(1)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur1 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(2)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur2 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(3)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur3 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(4)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur4 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(5)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur5 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(6)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur6 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(7)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur7 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(8)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur8 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(9)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur9 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(10)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur10 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(11)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur11 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(12)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorDur12 = pRaster.CreateCursor
    
    Set pRasterLayer = pMap.Layer(13)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol1 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(14)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol2 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(15)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol3 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(16)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol4 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(17)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol5 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(18)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol6 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(19)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol7 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(20)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol8 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(21)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol9 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(22)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol10 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(23)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol11 = pRaster.CreateCursor
    Set pRasterLayer = pMap.Layer(24)
    Set pRaster = pRasterLayer.Raster
    Set pRasterCursorSol12 = pRaster.CreateCursor
    
    startX = 407999.5
    startY = 4521978.5
    currX = startX
    currY = startY
    increment = 1
    pbCount = 0
    flushCount = 0
    'need an insertcursor & flush

    Do
    
        If pbCount > 0 Then
            pRasterCursorDur2.Next
            pRasterCursorDur3.Next
            pRasterCursorDur4.Next
            pRasterCursorDur5.Next
            pRasterCursorDur6.Next
            pRasterCursorDur7.Next
            pRasterCursorDur8.Next
            pRasterCursorDur9.Next
            pRasterCursorDur10.Next
            pRasterCursorDur11.Next
            pRasterCursorDur12.Next
            pRasterCursorSol1.Next
            pRasterCursorSol2.Next
            pRasterCursorSol3.Next
            pRasterCursorSol4.Next
            pRasterCursorSol5.Next
            pRasterCursorSol6.Next
            pRasterCursorSol7.Next
            pRasterCursorSol8.Next
            pRasterCursorSol9.Next
            pRasterCursorSol10.Next
            pRasterCursorSol11.Next
            pRasterCursorSol12.Next
        End If
            
        Set pPixelBlockDur1 = pRasterCursorDur1.PixelBlock
        Set pPixelBlockDur2 = pRasterCursorDur2.PixelBlock
        Set pPixelBlockDur3 = pRasterCursorDur3.PixelBlock
        Set pPixelBlockDur4 = pRasterCursorDur4.PixelBlock
        Set pPixelBlockDur5 = pRasterCursorDur5.PixelBlock
        Set pPixelBlockDur6 = pRasterCursorDur6.PixelBlock
        Set pPixelBlockDur7 = pRasterCursorDur7.PixelBlock
        Set pPixelBlockDur8 = pRasterCursorDur8.PixelBlock
        Set pPixelBlockDur9 = pRasterCursorDur9.PixelBlock
        Set pPixelBlockDur10 = pRasterCursorDur10.PixelBlock
        Set pPixelBlockDur11 = pRasterCursorDur11.PixelBlock
        Set pPixelBlockDur12 = pRasterCursorDur12.PixelBlock
        Set pPixelBlockSol1 = pRasterCursorSol1.PixelBlock
        Set pPixelBlockSol2 = pRasterCursorSol2.PixelBlock
        Set pPixelBlockSol3 = pRasterCursorSol3.PixelBlock
        Set pPixelBlockSol4 = pRasterCursorSol4.PixelBlock
        Set pPixelBlockSol5 = pRasterCursorSol5.PixelBlock
        Set pPixelBlockSol6 = pRasterCursorSol6.PixelBlock
        Set pPixelBlockSol7 = pRasterCursorSol7.PixelBlock
        Set pPixelBlockSol8 = pRasterCursorSol8.PixelBlock
        Set pPixelBlockSol9 = pRasterCursorSol9.PixelBlock
        Set pPixelBlockSol10 = pRasterCursorSol10.PixelBlock
        Set pPixelBlockSol11 = pRasterCursorSol11.PixelBlock
        Set pPixelBlockSol12 = pRasterCursorSol12.PixelBlock
        
        For y = 0 To pPixelBlockDur1.Height - 1
            'Debug.Print Now
            
            For x = 0 To pPixelBlockDur1.Width - 1

                Set pFeatureBuffer = pFC.CreateFeatureBuffer
                Set pPoint = New Point
                pPoint.x = currX
                pPoint.y = currY
                Set pFeatureBuffer.Shape = pPoint

                If pPixelBlockSol1.GetVal(0, x, y) > 0 Then
                    pFeatureBuffer.Value(2) = pPixelBlockSol1.GetVal(0, x, y)
                    pFeatureBuffer.Value(3) = pPixelBlockSol2.GetVal(0, x, y)
                    pFeatureBuffer.Value(4) = pPixelBlockSol3.GetVal(0, x, y)
                    pFeatureBuffer.Value(5) = pPixelBlockSol4.GetVal(0, x, y)
                    pFeatureBuffer.Value(6) = pPixelBlockSol5.GetVal(0, x, y)
                    pFeatureBuffer.Value(7) = pPixelBlockSol6.GetVal(0, x, y)
                    pFeatureBuffer.Value(8) = pPixelBlockSol7.GetVal(0, x, y)
                    pFeatureBuffer.Value(9) = pPixelBlockSol8.GetVal(0, x, y)
                    pFeatureBuffer.Value(10) = pPixelBlockSol9.GetVal(0, x, y)
                    pFeatureBuffer.Value(11) = pPixelBlockSol10.GetVal(0, x, y)
                    pFeatureBuffer.Value(12) = pPixelBlockSol11.GetVal(0, x, y)
                    pFeatureBuffer.Value(13) = pPixelBlockSol12.GetVal(0, x, y)
                    pFeatureBuffer.Value(14) = pPixelBlockDur1.GetVal(0, x, y)
                    pFeatureBuffer.Value(15) = pPixelBlockDur2.GetVal(0, x, y)
                    pFeatureBuffer.Value(16) = pPixelBlockDur3.GetVal(0, x, y)
                    pFeatureBuffer.Value(17) = pPixelBlockDur4.GetVal(0, x, y)
                    pFeatureBuffer.Value(18) = pPixelBlockDur5.GetVal(0, x, y)
                    pFeatureBuffer.Value(19) = pPixelBlockDur6.GetVal(0, x, y)
                    pFeatureBuffer.Value(20) = pPixelBlockDur7.GetVal(0, x, y)
                    pFeatureBuffer.Value(21) = pPixelBlockDur8.GetVal(0, x, y)
                    pFeatureBuffer.Value(22) = pPixelBlockDur9.GetVal(0, x, y)
                    pFeatureBuffer.Value(23) = pPixelBlockDur10.GetVal(0, x, y)
                    pFeatureBuffer.Value(24) = pPixelBlockDur11.GetVal(0, x, y)
                    pFeatureBuffer.Value(25) = pPixelBlockDur12.GetVal(0, x, y)


                    'sum solar radiation into annual total
                    pFeatureBuffer.Value(26) = pPixelBlockSol1.GetVal(0, x, y) + pPixelBlockSol2.GetVal(0, x, y) + _
                                                                  pPixelBlockSol3.GetVal(0, x, y) + pPixelBlockSol4.GetVal(0, x, y) + _
                                                                  pPixelBlockSol5.GetVal(0, x, y) + pPixelBlockSol6.GetVal(0, x, y) + _
                                                                  pPixelBlockSol7.GetVal(0, x, y) + pPixelBlockSol8.GetVal(0, x, y) + _
                                                                  pPixelBlockSol9.GetVal(0, x, y) + pPixelBlockSol10.GetVal(0, x, y) + _
                                                                  pPixelBlockSol11.GetVal(0, x, y) + pPixelBlockSol12.GetVal(0, x, y)

                    'sum sunlit duration into annual total
                    pFeatureBuffer.Value(27) = pPixelBlockDur1.GetVal(0, x, y) + pPixelBlockDur2.GetVal(0, x, y) + _
                                                                  pPixelBlockDur3.GetVal(0, x, y) + pPixelBlockDur4.GetVal(0, x, y) + _
                                                                  pPixelBlockDur5.GetVal(0, x, y) + pPixelBlockDur6.GetVal(0, x, y) + _
                                                                  pPixelBlockDur7.GetVal(0, x, y) + pPixelBlockDur8.GetVal(0, x, y) + _
                                                                  pPixelBlockDur9.GetVal(0, x, y) + pPixelBlockDur10.GetVal(0, x, y) + _
                                                                  pPixelBlockDur11.GetVal(0, x, y) + pPixelBlockDur12.GetVal(0, x, y)

                End If
            
                If pFeatureBuffer.Value(2) >= 0 Or _
                   pFeatureBuffer.Value(3) >= 0 Or _
                   pFeatureBuffer.Value(4) >= 0 Or _
                   pFeatureBuffer.Value(5) >= 0 Or _
                   pFeatureBuffer.Value(6) >= 0 Or _
                   pFeatureBuffer.Value(7) >= 0 Or _
                   pFeatureBuffer.Value(8) >= 0 Or _
                   pFeatureBuffer.Value(9) >= 0 Or _
                   pFeatureBuffer.Value(10) >= 0 Or _
                   pFeatureBuffer.Value(11) >= 0 Or _
                   pFeatureBuffer.Value(13) >= 0 Or _
                   pFeatureBuffer.Value(13) >= 0 Or _
                   pFeatureBuffer.Value(14) >= 0 Or _
                   pFeatureBuffer.Value(15) >= 0 Or _
                   pFeatureBuffer.Value(16) >= 0 Or _
                   pFeatureBuffer.Value(17) >= 0 Or _
                   pFeatureBuffer.Value(18) >= 0 Or _
                   pFeatureBuffer.Value(19) >= 0 Or _
                   pFeatureBuffer.Value(20) >= 0 Or _
                   pFeatureBuffer.Value(21) >= 0 Or _
                   pFeatureBuffer.Value(22) >= 0 Or _
                   pFeatureBuffer.Value(23) >= 0 Or _
                   pFeatureBuffer.Value(24) >= 0 Or _
                   pFeatureBuffer.Value(25) >= 0 Then
                   
                        pFCursor.InsertFeature pFeatureBuffer
                        flushCount = flushCount + 1
                        If CLng(flushCount / 20000) = 1 Then
                            Debug.Print "flush " & Now; pbCount; x; y
                            pFCursor.Flush
                            flushCount = 0
                        End If
                End If
                currX = currX + increment
                
            Next x
            Debug.Print Now
            currY = currY - increment
            currX = startX
        Next y
               
        pbCount = pbCount + 1
    
    Loop Until Not pRasterCursorDur1.Next
    pFCursor.Flush
End Sub


Users' Comments  
 

No comment posted

Add your comment

06, Aug. 2009
Last Updated ( 17, Aug. 2009 )
 
< Prev   Next >

AGRC Contacts | UGIC Contacts

feed image feed image

Utah GIS Portal © 2009 AGRC

Optimized for