PR

大きなDSM(GeoTIFF)の min / max / average を算出する

未分類

ドローン測量や航空測量で生成される DSM(Digital Surface Model) は、
GeoTIFF形式で保存されることが多く、1ファイルが 数GB〜数十GB になることも珍しくありません。

この記事では、DSM(GeoTIFF)から min / max / average(最小・最大・平均標高)を算出するというシンプルな処理を例に、チャンクを 使わない場合、チャンクを 使う場合の違いを、実務視点で分かりやすく整理します。

DSMとは何か(簡単に)

DSMは、地面の標高、建物、樹木などを 含んだ表面の高さ を表すラスターデータです。
GeoTIFFでは、画素(ピクセル)ごとに標高値が格納されています。

今回の目的

対象となる 巨大なDSM(GeoTIFF) に対して、以下を求めます。

  • min:最も低い標高
  • max:最も高い標高
  • average:平均標高

※GDALコマンドで取得できますが、チャンクの説明の例として上記を求めます

チャンクを使わない場合(全読み込み)

処理の流れ

  1. GeoTIFFを 一気に全読み込み
  2. すべての画素をメモリ上に展開
  3. min / max / average を計算

メリット

  • 実装がとてもシンプル
  • 小さなDSMなら高速

デメリット(重要)

  • DSMが大きいと メモリ不足
  • 数GB以上で MemoryError
  • Lambdaなどではほぼ不可能

向いているケース

  • 数百MB以下
  • ローカルPC
  • 試験的な処理

チャンクを使う場合(分割読み込み)

処理の流れ

  1. DSMを 小さなブロック(チャンク) に分割
  2. 1チャンクずつ読み込む
  3. 各チャンクで
    • min / max
    • 合計値
    • ピクセル数
      を更新
  4. 全チャンク処理後に average を計算

ポイント

  • 全体を一度に持たない
  • 常に一定メモリで動作

チャンク処理時の考え方(擬似的)

global_min = +∞
global_max = -∞
sum = 0
count = 0

for each chunk:
    local_min = min(chunk)
    local_max = max(chunk)
    global_min = min(global_min, local_min)
    global_max = max(global_max, local_max)

    sum += sum(chunk)
    count += number_of_pixels(chunk)

average = sum / count

平均値は「最後に1回だけ」計算するのがポイントです。

チャンクあり/なしの比較

観点チャンクなしチャンクあり
実装簡単やや複雑
メモリ使用量非常に大きい一定
大容量DSM
Lambda/Fargate
再開性
実務向き

実務でチャンク処理が必須になる場面

✔ AWS Lambda

  • メモリ制限(数GB)
  • 一括読み込みは危険

✔ EC2 / Fargate

  • 並列化しやすい
  • タイル単位で分散処理可能

✔ 複数DSMの一括処理

  • 夜間バッチ
  • 数十〜数百ファイル

チャンクサイズはどう決める?

目安として:

  • 512×512
  • 1024×1024

あたりがバランス良好です。

考慮ポイント:

  • メモリ使用量
  • I/O回数
  • 並列処理のしやすさ

よくある注意点

NoData値

  • DSMには NoData(欠損値) が含まれる
  • min / max / average 計算前に除外必須

オーバーフロー

  • sum は 64bit(float64) 推奨

サンプルコード(完成形)

import rasterio
import numpy as np

def calculate_dsm_stats(
    geotiff_path: str,
    chunk_width: int = 1024,
    chunk_height: int = 1024,
):
    """
    DSM(GeoTIFF)から min / max / average を
    チャンク処理で算出する関数
    """

    global_min = np.inf
    global_max = -np.inf
    total_sum = 0.0
    total_count = 0

    with rasterio.open(geotiff_path) as ds:
        nodata = ds.nodata
        width = ds.width
        height = ds.height

        # チャンク単位で走査
        for top in range(0, height, chunk_height):
            for left in range(0, width, chunk_width):

                # 実際のウィンドウサイズ(端数対応)
                w = min(chunk_width, width - left)
                h = min(chunk_height, height - top)

                window = rasterio.windows.Window(
                    col_off=left,
                    row_off=top,
                    width=w,
                    height=h
                )

                # 1チャンク読み込み
                data = ds.read(1, window=window)

                # NoData除外
                if nodata is not None:
                    data = data[data != nodata]

                # 有効データがない場合はスキップ
                if data.size == 0:
                    continue

                # float64 に変換(安全)
                data = data.astype(np.float64)

                # チャンク内統計
                local_min = data.min()
                local_max = data.max()
                local_sum = data.sum()
                local_count = data.size

                # 全体統計を更新
                global_min = min(global_min, local_min)
                global_max = max(global_max, local_max)
                total_sum += local_sum
                total_count += local_count

    # 平均値(ゼロ除算防止)
    if total_count == 0:
        raise ValueError("有効なピクセルが存在しません")

    average = total_sum / total_count

    return {
        "min": float(global_min),
        "max": float(global_max),
        "average": float(average),
        "count": int(total_count),
    }


if __name__ == "__main__":
    geotiff_path = "example_dsm.tif"

    stats = calculate_dsm_stats(
        geotiff_path,
        chunk_width=1024,
        chunk_height=1024,
    )

    print("DSM Statistics")
    print(f"Min     : {stats['min']}")
    print(f"Max     : {stats['max']}")
    print(f"Average : {stats['average']}")
    print(f"Count   : {stats['count']}")

まとめ

  • DSM(GeoTIFF)は 大きくなる前提で設計する
  • min / max / average のような単純処理でも
    チャンク処理が実務では必須
  • チャンク化により
    • メモリ安全
    • 再開可能
    • クラウド向き

👉 「まずチャンクで考える」 が、DSM処理を安定させる最大のコツです。

タイトルとURLをコピーしました