如何遍历连续栅格中的每个像元?


13

有关更多详细信息,请参见此链接

问题:

我想遍历一个连续的栅格(一个没有属性表的栅格),逐个像元,并获取像元的值。我想采用这些值并对它们运行条件,模拟下面详细介绍的地图代数步骤,而无需实际使用栅格计算器。

根据下面的评论请求,我添加了详细信息,为问题提供了背景,并证明有必要在下面称为“所需的分析:”的部分中实施这样的方法。

下面提出的分析虽然通过提供背景与我的问题相关,但无需在答案中进行。问题的范围仅涉及对连续栅格进行迭代以获取/设置像元值。

分析需要:

如果满足以下任一条件,则将输出单元格的值设置为1。如果不满足任何条件,则仅将输出单元格的值设置为0。

条件1:如果单元格值大于顶部和底部单元格,则给出值1:

Con("raster" > FocalStatistics("raster", NbrIrregular("C:\filepath\kernel_file.txt"), "MAXIMUM"), 1, 0)

内核文件如下所示:

3 3 
0 1 0
0 0 0
0 1 0

条件2:如果单元格值大于左右单元格,则给定值1:

Con("raster" > FocalStatistics("raster", NbrIrregular("C:\filepath\kernel_file.txt"), "MAXIMUM"), 1, 0)

内核文件如下所示:

3 3 
0 0 0
1 0 1
0 0 0  

条件3:如果单元格的值大于左上角和右下角的单元格,则将值设为1:

Con("raster" > FocalStatistics("raster", NbrIrregular("C:\filepath\kernel_file.txt"), "MAXIMUM"), 1, 0)

内核文件如下所示:

3 3 
1 0 0
0 0 0
0 0 1 

条件4:如果单元格值大于左下和右上单元格,则将值设为1:

Con("raster" > FocalStatistics("raster", NbrIrregular("C:\filepath\kernel_file.txt"), "MAXIMUM"), 1, 0)

内核文件如下所示:

3 3 
0 0 1
0 0 0
1 0 0 

条件5:如果任何相邻像元的值等于中心像元的值,则将输出栅格的值设为1(使用具有两次最近邻域计算的焦点变化

为什么不使用地图代数?

下文已指出,可以使用地图代数解决我的问题,但是如上所示,这是总共六个栅格计算,再加上一个将所有创建的栅格合并在一起的计算。在我看来,这是很多更有效的去细胞通过细胞和做所有比较的同时在每个单元而不是通过各自单独七次循环和利用相当多的内存来创建7个栅格。

应该如何解决该问题?

上面的链接建议使用IPixelBlock接口,但是从ESRI文档中尚不清楚您是通过IPixelBlock本身访问单个单元格值,还是从设置的IPixelBlock大小访问多个单元格值。一个好的答案应该提出一种访问连续栅格像元值的方法,并提供对代码背后的方法的解释(如果不是很明显的话)。

综上所述:

遍历连续栅格(没有属性表)中的每个像元以访问其像元值的最佳方法是什么?

一个好的答案不需要实现上述分析步骤,它仅需要提供一种访问栅格像元值的方法即可。


4
遍历栅格中的每个像元几乎总是没有必要的。您能否提供有关您要做什么的更多信息?
user2856 2013年

2
@Luke是正确的:到目前为止,在任何GIS中执行迭代栅格计算的最佳方法是避免显式地循环通过像元,因为在幕后,必须进行的所有循环均已得到优化。相反,如果可能的话,寻求一种使用GIS提供的地图代数功能的方法。如果要描述您的分析,您可能会获得使用这种方法的有用答案。
ub

@Luke我已添加了分析的详细信息。
2013年

1
谢谢您的澄清,Conor。我同意,如果您的GIS每次栅格计算都产生大量开销,那么编写自己的循环可能会更有效。出于好奇,对这套(异常)条件的预期解释是什么?
ub

1
@whuber用于边缘检测操作,可从我的栅格创建矢量多边形。该应用程序在概念上类似于从DEM(上面列出的邻域统计数据中的中心单元视为水将从其向下倾斜的“峰值”中识别水文盆地),但不在水文领域之外。我以前一直使用“流向”和“流域栅格”来实现此目的,但是由于这些方法的属性并不完全符合我的需要,因此在最终分析中容易出错。
2013年

Answers:


11

我看到原始海报(OP)已经解决了这个问题,但是我将在python中发布一个简单的解决方案,以防万一将来有人对解决此问题的不同方式感兴趣。我偏爱开源软件,因此这是在python中使用GDAL的解决方案:

import gdal

#Set GeoTiff driver
driver = gdal.GetDriverByName("GTiff")
driver.Register()

#Open raster and read number of rows, columns, bands
dataset = gdal.Open(filepath)
cols = dataset.RasterXSize
rows = dataset.RasterYSize
allBands = dataset.RasterCount
band = dataset.GetRasterBand(1)

#Get array of raster cell values.  The two zeros tell the 
#iterator which cell to start on and the 'cols' and 'rows' 
#tell the iterator to iterate through all columns and all rows.
def get_raster_cells(band,cols,rows):
    return band.ReadAsArray(0,0,cols,rows)

实现如下功能:

#Bind array to a variable
rasterData = get_raster_cells(band,cols,rows)

#The array will look something like this if you print it
print rasterData
> [[ 1, 2, 3 ],
   [ 4, 5, 6 ],
   [ 7, 8, 9 ]]

然后,使用嵌套循环遍历数据:

for row in rasterData:
    for val in row:
        print val
> 1
  2
  3
  4...

或者,也许您想通过列表理解来平整二维数组:

flat = [val for row in rasterData for val in row]

无论如何,在逐个单元地遍历数据的同时,有可能将一些条件放入循环中以更改/编辑值。请参阅我为不同的数据访问方式编写的脚本:https : //github.com/azgs/hazards-viewer/blob/master/python/zonal_stats.py


我喜欢此解决方案的简洁性和优雅性。我将再等几天,如果没有其他人提出相同或更高质量的解决方案,我将添加标签以扩大问题的范围,以造福社区,并为您提供赏金。
2013年

谢谢@Conor!我们在本周早些时候的工作场所遇到了类似的问题,因此我通过使用GDAL / python编写了一个类来解决了该问题。具体来说,我们需要一种服务器端方法来计算栅格区域的平均值,该方法仅在客户端应用程序上来自用户的边界框内给出。如果我添加其余的课程,您认为这会有所帮助吗?
asonnenschein 2013年

添加显示如何读取检索到的二维数组并编辑其值的代码将很有帮助。
2013年

9

更新!numpy的解决方案:

import arcpy
import numpy as np

in_ras = path + "/rastername"

raster_Array = arcpy.RasterToNumPyArray(in_ras)
row_num = raster_Array.shape[0]
col_num = raster_Array.shape[1]
cell_count = row_num * row_num

row = 0
col = 0
temp_it = 0

while temp_it < cell_count:
    # Insert conditional statements
    if raster_Array[row, col] > 0:
        # Do something
        val = raster_Array[row, col]
        print val
    row+=1
    if col > col_num - 1:
        row = 0
        col+=1

因此,使用arcpy将完成的阵列返回到栅格很麻烦。arcpy.NumPyArrayToRaster是松散的,即使您将其输入LL坐标,也倾向于重新定义范围。

我更喜欢另存为文本。

np.savetxt(path + "output.txt", output, fmt='%.10f', delimiter = " ")

我正在以64位速度运行Python-截至目前,这意味着我无法向numpy.savetxt提供标头。因此,在将ASCII转换为Raster之前,我必须打开输出并添加Arc想要的ASCII标头

File_header = "NCOLS xxx" + '\n'+ "NROWS xxx" + '\n' + "XLLCORNER xxx"+'\n'+"YLLCORNER xxx"+'\n'+"CELLSIZE xxx"+'\n'+"NODATA_VALUE xxx"+'\n'

numpy版本运行我的移位栅格,乘法和加法比arcpy版本(2分钟内执行1000次迭代)要快得多(2分钟内进行1000次迭代)。

旧版本我稍后可能会删除它,我刚刚编写了类似的脚本。我尝试转换为点并使用搜索光标。我在12小时内只有5000次迭代。因此,我正在寻找另一种方式。

我这样做的方法是遍历每个单元格的单元格中心坐标。我从左上角开始,然后从右向左移动。在该行的结尾,我向下移动一行,然后从左侧重新开始。我有一个240 m栅格,其中包含2603列和2438行,因此共有6111844个像元。我使用了迭代器变量和while循环。见下文

一些注意事项:1-您需要知道范围的坐标

2-以像元中心的点坐标运行-从范围值移动像元大小的1/2

3-我的脚本正在使用像元值提取特定于值的栅格,然后将该栅格移动到原始像元的中心。这将添加到零栅格以扩展范围,然后再添加到最终栅格。这只是一个例子。您可以将条件语句放在此处(while循环内的第二条if语句)。

4-该脚本假定所有栅格值都可以转换为整数。这意味着您需要首先清除无数据。骗子IsNull。

6-我对此仍然不满意,我正在努力将其完全从arcpy中删除。我宁愿将其转换为numpy数组并在那里进行数学运算,然后将其带回Arc。

ULx = 959415 ## coordinates for the Upper Left of the entire raster 
ULy = 2044545
x = ULx ## I redefine these if I want to run over a smaller area
y = ULy
temp_it = 0

while temp_it < 6111844: # Total cell count in the data extent
        if x <= 1583895 and y >= 1459474: # Coordinates for the lower right corner of the raster
           # Get the Cell Value
           val_result = arcpy.GetCellValue_management(inraster, str(x)+" " +str(y), "1")
           val = int(val_result.getOutput(0))
        if val > 0: ## Here you could insert your conditional statements
            val_pdf = Raster(path + "pdf_"str(val))
            shift_x  =  ULx - x # This will be a negative value
            shift_y = ULy - y # This will be a positive value
            arcpy.Shift_management(val_pdf, path+ "val_pdf_shift", str(-shift_x), str(-shift_y))
            val_pdf_shift = Raster(path + "val_pdf_shift")
            val_pdf_sh_exp = CellStatistics([zeros, val_pdf_shift], "SUM", "DATA")
            distr_days = Plus(val_pdf_sh_exp, distr_days)
        if temp_it % 20000 == 0: # Just a print statement to tell me how it's going
                print "Iteration number " + str(temp_it) +" completed at " + str(time_it)
        x += 240 # shift x over one column
        if x > 1538295: # if your at the right hand side of a row
            y = y-240 # Shift y down a row
            x = 959415 # Shift x back to the first left hand column
        temp_it+=1

distr_days.save(path + "Final_distr_days")

4

尝试使用IGridTable,ICursor,IRow。此代码段用于更新栅格像元值,但是它显示了迭代的基础:

如何在栅格属性表中添加新字段并循环遍历?

Public Sub CalculateArea(raster As IRaster, areaField As String)
    Dim bandCol As IRasterBandCollection
    Dim band As IRasterBand

    Set bandCol = raster
    Set band = bandCol.Item(0)

    Dim hasTable As Boolean
    band.hasTable hasTable
    If (hasTable = False) Then
        Exit Sub
    End If    

    If (AddVatField(raster, areaField, esriFieldTypeDouble, 38) = True) Then
        ' calculate cell size
        Dim rstProps As IRasterProps
        Set rstProps = raster

        Dim pnt As IPnt
        Set pnt = rstProps.MeanCellSize

        Dim cellSize As Double
        cellSize = (pnt.X + pnt.Y) / 2#

        ' get fields index
        Dim attTable As ITable
        Set attTable = band.AttributeTable

        Dim idxArea As Long, idxCount As Long
        idxArea = attTable.FindField(areaField)
        idxCount = attTable.FindField("COUNT")

        ' using update cursor
        Dim gridTableOp As IGridTableOp
        Set gridTableOp = New gridTableOp

        Dim cellCount As Long, cellArea As Double

        Dim updateCursor As ICursor, updateRow As IRow
        Set updateCursor = gridTableOp.Update(band.RasterDataset, Nothing, False)
        Set updateRow = updateCursor.NextRow()
        Do Until updateRow Is Nothing
            cellCount = CLng(updateRow.Value(idxCount))
            cellArea = cellCount * (cellSize * cellSize)

            updateRow.Value(idxArea) = cellArea
            updateCursor.updateRow updateRow

            Set updateRow = updateCursor.NextRow()
        Loop

    End If
End Sub

遍历表格后,您可以使用来获取特定字段行的值row.get_Value(yourfieldIndex)。如果您是Google

arcobjects row.get_Value

您应该能够得到大量的例子来说明这一点。

希望能有所帮助。


1
不幸的是,我忽略了要注意的问题,我将在上面的原始问题中进行编辑,即我的栅格具有许多由大的double值组成的连续值,因此该方法将不起作用,因为我的栅格没有属性表值。
2013年

4

作为一个激进的想法怎么样,它将需要您使用python或ArcObjects进行编程。

  1. 将网格转换为点要素类。
  2. 创建XY字段并填充。
  3. 将这些点加载到字典中,其中key是X,Y的字符串,item是单元格的值。
  4. 逐步浏览字典,为每个点计算出周围的8个单元格XY。
  5. 从字典中检索这些值并使用您的规则进行测试,一旦找到一个真值,就可以跳过其余测试。
  6. 将结果写到另一本词典中,然后通过首先创建一个点FeatureClass然后再将点转换为网格将其转换回网格。

2
通过转换为一组点特征,此想法消除了使其如此有效的基于栅格的数据表示的两种质量:(1)查找邻居是极其简单的恒定时间操作,并且(2)因为显式存储位置是不需要,RAM,磁盘和I / O需求最少。因此,尽管这种方法行之有效,但很难找到推荐它的任何理由。
ub

感谢您的回答Hornbydd。我可以实现这样的方法,但是步骤4和步骤5似乎不是非常有效的计算方法。我的栅格将至少有62,500个像元(我为栅格设置的最小分辨率为250个像元x 250个像元,但分辨率通常可以包含更多),并且我必须进行空间查询对于执行我的比较的每个条件...由于我有6个条件,因此将是6 * 62500 = 375000空间查询。我最好选择地图代数。但是,感谢您以新的方式查看问题。已投票。
Conor 2013年

您不能将其转换为ASCII,然后使用R之类的程序进行计算吗?
奥利弗·伯德金

另外,我有一个Java applet,可以很容易地对其进行修改以满足您的上述条件。这只是一个平滑算法,但是更新非常容易。
奥利弗·伯德金

只要可以从.NET平台为仅安装.NET Framework 3.5和ArcGIS 10的用户调用该程序。该程序是开源的,我打算将这些程序交付给最终用户时成为唯一的软件要求。如果您的答案可以满足这两个要求,那么它将被视为有效答案。我还将在问题中添加一个版本标签以进行说明。
Conor 2013年

2

一个办法:

我今天早些时候解决了这个问题。该代码是对此方法。一旦我弄清楚了用于与栅格接口的对象实际所做的工作,这个概念就并不困难。下面的方法采用两个输入数据集(inRasterDS和outRasterDS)。它们都是相同的数据集,我只是复制了inRasterDS并将其作为outRasterDS传递给了方法。这样,它们都具有相同的范围,空间参考等。该方法从inRasterDS逐个单元读取值,并对它们进行最近邻比较。它使用这些比较的结果作为outRasterDS中的存储值。

流程:

我使用IRasterCursor-> IPixelBlock-> SafeArray来获取像素值,并使用IRasterEdit将新值写入栅格。创建IPixelBlock时,是在告诉机器要读取/写入的区域的大小和位置。如果只想编辑栅格的下半部分,则将其设置为IPixelBlock参数。如果要遍历整个栅格,则必须将IPixelBlock设置为等于整个栅格的大小。我通过在下面的方法中执行此操作,方法是将大小传递给IRasterCursor(pSize),然后从光栅光标获取PixelBlock。

另一个关键是您必须使用SafeArray与该方法中的值进行接口。您可以从IRasterCursor获取IPixelBlock,然后从IPixelBlock获取SafeArray。然后,您读写SafeArray。完成对SafeArray的读/写操作后,将整个SafeArray写回到IPixelBlock,然后将IPixelBlock写到IRasterCursor,最后使用IRasterCursor设置开始写入的位置,并使用IRasterEdit进行写入。最后一步是您实际编辑数据集值的地方。

    public static void CreateBoundaryRaster(IRasterDataset2 inRasterDS, IRasterDataset2 outRasterDS)
    {
        try
        {
            //Create a raster. 
            IRaster2 inRaster = inRasterDS.CreateFullRaster() as IRaster2; //Create dataset from input raster
            IRaster2 outRaster = outRasterDS.CreateFullRaster() as IRaster2; //Create dataset from output raster
            IRasterProps pInRasterProps = (IRasterProps)inRaster;
            //Create a raster cursor with a pixel block size matching the extent of the input raster
            IPnt pSize = new DblPnt();
            pSize.SetCoords(pInRasterProps.Width, pInRasterProps.Height); //Give the size of the raster as a IPnt to pass to IRasterCursor
            IRasterCursor inrasterCursor = inRaster.CreateCursorEx(pSize); //Create IRasterCursor to parse input raster 
            IRasterCursor outRasterCursor = outRaster.CreateCursorEx(pSize); //Create IRasterCursor to parse output raster
            //Declare IRasterEdit, used to write the new values to raster
            IRasterEdit rasterEdit = outRaster as IRasterEdit;
            IRasterBandCollection inbands = inRasterDS as IRasterBandCollection;//set input raster as IRasterBandCollection
            IRasterBandCollection outbands = outRasterDS as IRasterBandCollection;//set output raster as IRasterBandCollection
            IPixelBlock3 inpixelblock3 = null; //declare input raster IPixelBlock
            IPixelBlock3 outpixelblock3 = null; //declare output raster IPixelBlock
            long blockwidth = 0; //store # of columns of raster
            long blockheight = 0; //store # of rows of raster

            //create system array for input/output raster. System array is used to interface with values directly. It is a grid that overlays your IPixelBlock which in turn overlays your raster.
            System.Array inpixels; 
            System.Array outpixels; 
            IPnt tlc = null; //set the top left corner

            // define the 3x3 neighborhood objects
            object center;
            object topleft;
            object topmiddle;
            object topright;
            object middleleft;
            object middleright;
            object bottomleft;
            object bottommiddle;
            object bottomright;

            long bandCount = outbands.Count; //use for multiple bands (only one in this case)

            do
            {

                inpixelblock3 = inrasterCursor.PixelBlock as IPixelBlock3; //get the pixel block from raster cursor
                outpixelblock3 = outRasterCursor.PixelBlock as IPixelBlock3;
                blockwidth = inpixelblock3.Width; //set the # of columns in raster
                blockheight = inpixelblock3.Height; //set the # of rows in raster
                outpixelblock3.Mask(255); //set any NoData values

                for (int k = 0; k < bandCount; k++) //for every band in raster (will always be 1 in this case)
                {
                    //Get the pixel array.
                    inpixels = (System.Array)inpixelblock3.get_PixelData(k); //store the raster values in a System Array to read
                    outpixels = (System.Array)outpixelblock3.get_PixelData(k); //store the raster values in a System Array to write
                    for (long i = 1; i < blockwidth - 1; i++) //for every column (except outside columns)
                    {
                        for (long j = 1; j < blockheight - 1; j++) //for every row (except outside rows)
                        {
                            //Get the pixel values of center cell and  neighboring cells

                            center = inpixels.GetValue(i, j);

                            topleft = inpixels.GetValue(i - 1, j + 1);
                            topmiddle = inpixels.GetValue(i, j + 1);
                            topright = inpixels.GetValue(i + 1, j + 1);
                            middleleft = inpixels.GetValue(i - 1, j);
                            middleright = inpixels.GetValue(i + 1, j);
                            bottomleft = inpixels.GetValue(i - 1, j - 1);
                            bottommiddle = inpixels.GetValue(i, j - 1);
                            bottomright = inpixels.GetValue(i - 1, j - 1);


                            //compare center cell value with middle left cell and middle right cell in a 3x3 grid. If true, give output raster value of 1
                            if ((Convert.ToDouble(center) >= Convert.ToDouble(middleleft)) && (Convert.ToDouble(center) >= Convert.ToDouble(middleright)))
                            {
                                outpixels.SetValue(1, i, j);
                            }


                            //compare center cell value with top middle and bottom middle cell in a 3x3 grid. If true, give output raster value of 1
                            else if ((Convert.ToDouble(center) >= Convert.ToDouble(topmiddle)) && (Convert.ToDouble(center) >= Convert.ToDouble(bottommiddle)))
                            {
                                outpixels.SetValue(1, i, j);
                            }

                            //if neither conditions are true, give raster value of 0
                            else
                            {

                                outpixels.SetValue(0, i, j);
                            }
                        }
                    }
                    //Write the pixel array to the pixel block.
                    outpixelblock3.set_PixelData(k, outpixels);
                }
                //Finally, write the pixel block back to the raster.
                tlc = outRasterCursor.TopLeft;
                rasterEdit.Write(tlc, (IPixelBlock)outpixelblock3);
            }
            while (inrasterCursor.Next() == true && outRasterCursor.Next() == true);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(rasterEdit);


        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }

    }

1

可以通过三种方式读取AFAIK栅格数据:

  • 按单元(效率低下);
  • 按图像(相当有效);
  • 按块(最有效的方式)。

在不重新设计轮子的情况下,我建议阅读Chris Garrard的这些启发性幻灯片

因此,最有效的方法是逐块读取数据,但是在应用滤波器时,这将导致位于块边界上的像素对应数据丢失。因此,一种安全的替代方法应包括一次读取整个图像并使用numpy方法。

在计算方面,我应该使用gdalfilter.py并隐式使用VRT KernelFilteredSource方法,以便应用所需的过滤器,最重要的是,避免进行繁重的计算。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.