連続ラスタのすべてのセルを反復処理するにはどうすればよいですか?


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:セルの値がbottomleftおよびtoprightのセルより大きい場合、値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の値は、ラスタ出力を与える(2つの最も近い近隣の計算と焦点さまざまな方法を使って

なぜマップ代数を使用しないのですか?

以下では、マップ代数を使用して問題を解決できることに注意しましたが、上記のように、これは合計6つのラスター計算と、作成されたすべてのラスターを結合する1つです。私は、セルごとに移動し、各セルを個別に7回ループして7つのラスターを作成するためにかなりのメモリを使用する代わりに、各セルですべての比較を一度に行う方がはるかに効率的であるように思われます。

問題はどのように攻撃されるべきですか?

上記のリンクではIPixelBlockインターフェイスを使用することをお勧めしますが、実際にIPixelBlockを介して単一のセル値にアクセスしているのか、設定したIPixelBlockのサイズから複数のセル値にアクセスしているのかは、ESRIのドキュメントからはわかりません。良い答えは、連続ラスタのセル値にアクセスする方法を提案し、明らかでない場合はコードの背後にある方法論の説明を提供する必要があります。

要約すれば:

CONTINUOUSラスタ(属性テーブルを持たない)のすべてのセルをループしてセル値にアクセスする最良の方法は何ですか?

良い答えは、上記の解析ステップを実装する必要はなく、ラスタのセル値にアクセスする方法を提供するだけです。


4
ラスタ内のすべてのセルをループ処理することは、ほとんど常に不要です。あなたがやろうとしていることに関する詳細情報を提供できますか?
user2856

2
@Lukeは正しいです。GISで反復ラスタ計算を実行する最も良い方法は、明示的にセルをループすること避けることです。代わりに、可能であれば、GISが提供するマップ代数機能を使用する方法を探してください。分析を説明する場合、そのようなアプローチを使用する有用な回答が得られる場合があります。
whuber

@Luke分析の詳細を追加しました。
コナー

1
説明をありがとう、コナー。GISが各ラスタ計算でかなりのオーバーヘッドを被る場合、独自のループを記述する方が効率的である可能性があることに同意します。好奇心から、この(異常な)条件のセットの意図された解釈は何ですか?
whuber

1
@whuberエッジ検出操作のために、ラスターからベクターポリゴンを作成します。このアプリケーションは、DEMから水文学盆地を識別することに概念的に似ています(上記の近隣統計の中央セルを、水が下り坂から流れる「ピーク」と考えてください)が、水文学の分野の外にあります。以前はこの目的でFlow DirectionとBasin Rasterを使用していましたが、これらのメソッドのプロパティが正確に必要なものではないため、最終分析でエラーが発生しやすくなります。
コナー

回答:


11

これは、Original Poster(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...

または、リスト内包表記を使用して2次元配列をフラット化することもできます。

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

とにかく、セルごとにデータを反復処理しながら、値を変更/編集するためにループにいくつかの条件をスローすることができます。:私は、データにアクセスするためのさまざまな方法のために書いた、このスクリプトを参照してくださいhttps://github.com/azgs/hazards-viewer/blob/master/python/zonal_stats.pyを


このソリューションのシンプルさとエレガントさが気に入っています。あと数日待ちますが、同等またはそれ以上の品質のソリューションが他にない場合は、タグを追加して、コミュニティの利益のために質問の範囲を広げ、賞金を授与します。
コナー

ありがとう、@ Conor!今週初めに私の職場で同様の問題に遭遇したので、GDAL / pythonでクラスを書くことでそれを解決しました。具体的には、クライアント側アプリケーションのユーザーからの境界ボックスのみを指定して、ラスターの面積の平均値を計算するサーバー側の方法が必要でした。私が書いた残りのクラスを追加すると有益だと思いますか?
アソネンシャイン

取得した2次元配列を読み取り、その値を編集する方法を示すコードを追加すると役立ちます。
コナー

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 = " ")

私はPythonを高速化のために64ビットとして実行しています-今のところ、これは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バージョン(15分で1000回の反復)よりもはるかに高速(2分で1000回の反復)シフトラスタ、乗算、および加算を実行します

古いバージョンこれは後で削除できますが、同様のスクリプトを作成しました。ポイントに変換して検索カーソルを使用してみました。12時間で5000回しか反復できませんでした。それで、私は別の方法を探しました。

これを行う私の方法は、各セルのセル中心座標を反復処理することです。左上隅から始めて、右から左に移動します。行の終わりで、私は行を下に移動し、左からやり直します。2603列と2438行の240 mラスターがあるため、合計で6111844のセルがあります。イテレータ変数とwhileループを使用します。下記参照

いくつかのメモ:1-範囲の座標を知る必要があります

2-セル中心のポイント座標で実行-範囲値からセルサイズの1/2移動

3-私のスクリプトは、セル値を使用して特定の値のラスタを取得し、このラスタを元のセルの中心に移動します。これにより、最終ラスタに追加する前に、ゼロラスタに追加して範囲を拡張します。これは単なる例です。ここに条件ステートメントを配置できます(whileループ内の2番目のifステートメント)。

4-このスクリプトは、すべてのラスタ値を整数としてキャストできることを前提としています。つまり、まずデータなしを取り除く必要があります。Con 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値で構成される連続値が多数あり、ラスターには属性テーブル値がないため、この方法は機能しないことに注意してください。
コナー

4

これは根本的なアイデアとしてはどうでしょうか。PythonまたはArcObjectsでプログラムする必要があります。

  1. グリッドをポイントフィーチャクラスに変換します。
  2. XYフィールドを作成して入力します。
  3. キーをX、Yの文字列、アイテムをセル値とするディクショナリにポイントをロードします。
  4. 辞書をステップスルーし、各ポイントについて、周囲の8つのセルXYを計算します。
  5. 辞書からこれらを取得し、ルールでテストします。trueの値が見つかったらすぐに、残りのテストをスキップできます。
  6. 結果を別のディクショナリに書き込み、最初にポイントFeatureClassを作成してからポイントをグリッドに変換することにより、グリッドに変換します。

2
このアイデアは、一連のポイントフィーチャに変換することにより、非常に効果的な2つの品質のラスターベースのデータ表現を排除します。(1)近隣の検索は非常に単純な一定時間の操作であり、(2)必要ありません。RAM、ディスク、およびI / Oの要件は最小限です。したがって、このアプローチは機能しますが、推奨する理由を見つけるのは困難です。
whuber

Hornbyddの回答に感謝します。このようなメソッドを実装しても問題ありませんが、ステップ4と5は計算上あまり効果的ではないようです。ラスタの最小セル数は62,500であり(設定したラスタの最小解像度は250セルx 250セルですが、解像度はさらに多くの場合で構成できます)、空間クエリを実行する必要があります比較を実行するすべての条件について... 6つの条件があるため、6 * 62500 = 375000空間クエリになります。私は地図代数を使ったほうが良いでしょう。しかし、この問題の新しい表示方法に感謝します。賛成。
コナー

ASCIIに変換してから、Rなどのプログラムを使用して計算を行うことはできませんか?
オリバーバーデキン

さらに、上記の条件を満たすように簡単に変更できるJavaアプレットを作成しました。それは単なる平滑化アルゴリズムでしたが、更新は非常に簡単です。
オリバーバーデキン

.NET Framework 3.5およびArcGIS 10のみがインストールされているユーザーに対して.NETプラットフォームからプログラムを呼び出すことができる限り。このプログラムはオープンソースであり、エンドユーザーに提供される場合、これらが唯一のソフトウェア要件になることを意図しています。これら2つの要件を満たすために回答を実装できる場合、それは有効な回答と見なされます。わかりやすくするために、質問にもバージョンタグを追加します。
コナー

2

解決策:

今日、これを早く解決しました。コードはこのメソッドの適応です。この背景にある概念は、ラスターとのインターフェイスに使用されるオブジェクトが実際に何をするかを理解すれば、それほど難しくありませんでした。以下のメソッドは、2つの入力データセット(inRasterDSおよびoutRasterDS)を取ります。どちらも同じデータセットです。inRasterDSのコピーを作成し、outRasterDSとしてメソッドに渡しました。このように、それらは両方とも同じ範囲、空間参照などを持ちます。このメソッドは、inRasterDSからセルごとに値を読み取り、それらの最近傍比較を行います。これらの比較の結果は、outRasterDSに格納された値として使用されます。

プロセス:

IRasterCursor-> IPixelBlock-> SafeArrayを使用してピクセル値を取得し、IRasterEditを使用して新しい値をラスターに書き込みました。IPixelBlockを作成すると、読み取り/書き込みを行う領域のサイズと場所をマシンに伝えます。ラスターの下半分のみを編集する場合は、IPixelBlockパラメーターとして設定します。ラスタ全体をループする場合は、IPixelBlockをラスタ全体のサイズに等しく設定する必要があります。これを以下の方法で行うには、サイズをIRasterCursor(pSize)に渡し、ラスターカーソルからPixelBlockを取得します。

もう1つの鍵は、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ラスターデータは、次の3つの方法で読み取ることができます。

  • セルごと(非効率的)。
  • 画像で(非常に効率的);
  • ブロック単位(最も効率的な方法)。

車輪を再発明することなく、クリス・ガラードのこれらの啓発的なスライドを読むことをお勧めします。

そのため、最も効率的な方法はブロックごとにデータを読み取ることですが、これにより、フィルターの適用中にブロックの境界を越えて位置するピクセルに対応するデータが失われます。したがって、安全な代替方法は、イメージ全体を一度に読み取り、numpyアプローチを使用することです。

計算面では、代わりに、必要なフィルターを適用し、何よりも重い計算を避けるために、gdalfilter.pyと暗黙的にVRT KernelFilteredSourceアプローチを使用する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.