検証記録 2026-09-20 / CS Map Sheets v0.5.2

修正元:
- CS_Map_Sheets_QGIS_Universal_v0.5.1.zip
- 指定変更により、v0.4.0 ZIPではなくv0.5.1試作版を基準とした。

自動テスト:
- 実行: python -m unittest discover -s csmap_sheets/tests -t . -v
- 結果: 67件中62件成功、5件スキップ、失敗0件。
- スキップ理由: この環境にGDAL Python bindings、PDAL、QGISがないため。
- Python構文: python -m compileall -q csmap_sheets 成功。

色修正の確認:
- 独自方式の代表RGBA画像 SHA-256:
  32448c448f375a16ac6a29321204af8540fd86e5f9ae566b985004e96199227b
- 修正前v0.5.1と修正後independent_v040について、追加の乱数DEM 5ケースのRGBA SHA-256が一致。
- FME 2色LUTの全端点、曲率Bの0/127/128/255、範囲外クリップ、曲率符号を確認。
- 5レイヤー重み合計1.0、既知値の手計算合成、ストレッチ端点と不正範囲拒否を確認。
- FMEモードの一括処理とブロック処理が画素一致。
- NoDataと外周アルファは既存テストで確認。
- run.json用renderingレコードに11色、5重み、正規化、ストレッチ、符号、平滑化、入力種別が含まれることを確認。

FME資料との照合:
- 公式マニュアルとFMWの11色、5重み、標高200～2000、傾斜0～60、RGBストレッチ値を照合。
- FMWの曲率核は本実装と同符号の5点ラプラシアン。FMWの除数にcell_size^2*0.01があるため、FME ±10と本実装±0.1 1/mが尺度上対応する。
- FMEの傾斜はHorn法、本プラグインは中央差分。平滑化条件も同一とは限らないため、FMEとの地形量・画素完全一致は未確認。

検証用データでの試験:
- 添付 FG-GML-533901-DEM1A-20250604(1).zip の先頭XMLで、既存GSIパーサが
  "Too many GSI tuples for GridEnvelope/startPoint" を返した。
- 再現XML: FG-GML-5339-01-00-DEM1A-20250604.xml
- GridEnvelope low=[0,0], high=[1124,749]、sequenceRule order=+x-y、startPoint=0 0、tuple数=843750。
- 色処理より前の入力不具合で、変更禁止範囲のため本修正には含めていない。
- 基盤地図情報の出典は、v0.9.0の節の「検証に使用したデータの出典」を参照。

未実施:
- Windows 10/11 + QGIS 3.44.xでのZIPインストールとGUI確認。
- GDALを使う全域VRT、図郭GeoTIFF、XYZ PNG、run.json completedの通し確認。
- FMEアプリケーションで同一DEMを処理した画像との画素比較。
- 元v0.4.0 ZIPとの直接画素比較（元ZIP未入手）。

人によるWindows/QGIS実証手順は同梱の修正検証報告書を参照。

v0.6.0（2026-09-24、Claude/ChatGPT両実装の突き合わせ統合）:
- Claudeが別途作成したv0.6.0相当の実装（同じCodex引継ぎ指示書に基づく並行実装）とコードを
  1行ずつ突き合わせ、以下を確認・修正した。
- バグ発見・修正：`validate_config`の裸のデフォルト値が`render_mode`未指定時も`curvature_limit=0.03`
  のままで、独自方式(v0.4互換)を謳いながら外部JSON等の経路では曲率値がFME方式の値に漏れていた。
  QGIS画面の組込みプリセットは常に0.05を上書きするため実害は限定的だったが、`test_independent_v040_regression_hash`
  も0.05を直接relief()へ渡しており、この経路のバグは検出していなかった。裸のデフォルトを0.05へ修正し、
  再現テスト(`ConfigDefaultsTests`)を追加。
- 組込みPROFILEに暫定プロファイル（曲率±0.03、1m）を追加（従来は数値欄手入力または外部JSON経由のみ）。
- FME方式のRGBストレッチ既定を「なし」から「長野県実績値」へ変更（マニュアル記載の色の鮮明化を
  初期状態で再現するため）。
- 上記のFMW曲率核照合（cell_size^2*0.01の除数からFME±10と本実装±0.1 1/mが尺度上対応するとの結論）は
  本統合作業では再検証していない。VALIDATION.txtの既存記載を引き継いだのみで、Claude側は独自には
  確認していない点に注意。

v0.6.0 追記（2026-09-24、Claude独自の.fmw直接検証）:
- 上記の「未検証」を解消：`66-0ca2-4055-b9a5-0b9b0b3731cb`（FMEワークスペース一式）を実際に展開し、
  `s2_DEMラスターからCS立体図への変換.fmw`のXMLを直接読んだ。
- 曲率計算：`RasterConvolver`（XFORMER_NAME=RasterConvolver_2）の
  `KERNEL_WEIGHTS="0 -1 0 -1 4 -1 0 -1 0"`・`KERNEL_DIVISOR="$(CELL_SIZE)**2 * 0.01"`を確認。
  本プラグインの`curvature = (4*smooth - 4近傍和) / cell_size**2`と同じ符号・形状のラプラシアン核だが、
  FME側は除数にさらに`0.01`を掛けている＝FME内部の曲率値は本プラグインの1/m値の100倍。
  よってFMEマニュアルの「曲率±10」＝本プラグインの「±0.1 1/m」との対応は**確認済み**（推測ではない）。
- 傾斜計算：`RasterSlopeCalculator`の`ALGORITHM="Horn"`・`OUTPUT_TYPE="Degrees"`を確認。
  本プラグインは中央差分（`np.roll`による単純な2点差分）であり、**Horn法とは計算方法が異なる**。
  傾斜由来の色（傾斜A・傾斜Bレイヤー）はこの差により、FME出力と画素完全一致しない可能性が高い。
  本パッチでは対応せず、傾斜計算方式の変更は別課題として扱う（指示書10章の変更禁止範囲外の
  地形量計算そのものに踏み込むため、色調修正の範囲を超えると判断）。
- 平滑化（曲率計算前のガウシアン相当）は別の`RasterConvolver`（XFORMER_NAME=RasterConvolver、
  除数="Sum of Kernel Weights"、重みは公開パラメータ`$(KERNEL_WEIGHTS)`経由）で行われており、
  具体的なカーネルサイズ・シグマは本検証では特定していない（別紙2の「19×19／3×3ガウシアン
  フィルター」との対応関係は未確認のまま）。

方針決定（2026-09-24）— 本家QGISプラグインとの関係:
- [waigania13/CSMapMaker](https://github.com/waigania13/CSMapMaker)（長野県林業総合センター
  戸田氏開発）のソース（csmap.py, csmap_make.py）を確認した。SAGA GISの`slopeaspectcurvature`
  （METHOD=6）で複数の測地形学的曲率（Plan・General等、既定は両者の組合せ）を計算し、
  ColorBrewer段階配色（例：Blues 9段階＋RdBu 5段階、各不透明度0.5）を持つ複数レイヤーを
  QGISキャンバス上で半透明合成して画像化する方式。RGB値の数式合成ではない。
- これはFMEマニュアルの5レイヤー固定RGB加重合成方式とは、曲率の定義・平滑化条件・配色方式の
  いずれも異なる別実装であることが判明した。
- 本プラグインは**FMEマニュアルへの準拠を継続**する方針を確定（ユーザー指示、2026-09-24）。
  waigania13版アルゴリズムの再現・作り直しは行わない。これまでの曲率飽和値検証（±0.1/±0.03、
  KERNEL_DIVISOR=cell_size²×0.01の照合）はFMEマニュアル側の仕様確認として引き続き有効。

追加確認（2026-09-24）— 林野庁公式マニュアル「CS立体図を使った地形判読マニュアル」(romou-12.pdf、
令和5年3月、林野庁発行)を確認:
- 本資料は森林・林業技術者向けの地形判読（利用）マニュアルであり、色のRGB値・合成比率・曲率閾値
  等の実装レベルの技術仕様は含まれていない。技術仕様の一次出典は戸田堅一郎氏の学術論文
  （砂防学会誌2012、森林立地2014、長野県林業総合センター研究報告2018、いずれも本PDF未収録）。
- 巻末【参考サイト】に、G空間情報センターで公開されているCS立体図作成ツールとして
  ArcGIS版・QGIS版（https://www.geospatial.jp/ckan/dataset/cs-tool）・
  FME版（https://www.geospatial.jp/ckan/dataset/fme-csmapmaker）の3つが並記されている。
- **FME版はこのマニュアルが紹介しているツールの1つ**であり、waigania13版
  （QGIS/SAGA版）の代替ではなく、独立した実装として位置づけられる。3系統は別々の
  開発者による独立実装であり、同マニュアルもこれらの技術的な完全一致を前提としていないとみられる。
  よって「FMEマニュアルへの準拠を継続する」方針（上記）は、同マニュアルが紹介する実装系統の1つを
  選んだものであり、妥当な根拠を持つ。

最終確認（2026-09-24）— 戸田堅一郎氏の原論文（砂防学会誌65(2), 2012／森林立地56(2), 2014）を直接確認:
- 2012年論文（砂防学会誌）に、微地形図の合成手順が正確に記載されていた：
  立体図＝[標高(黒⇔白)を透過率0%で下敷き]→[曲率(紺⇔白)を透過率50%で重ね]→[傾斜(白⇔茶)を透過率50%で重ね]。
  曲率図＝[曲率(青⇔赤)を透過率0%で下敷き]→[傾斜(白⇔黒)を透過率50%で重ね]。
  微地形図＝[立体図を透過率0%で下敷き]→[曲率図を透過率50%で重ね]→[等高線を透過率0%で重ね]。
- 「透過率t%で重ねる」を「新層×(1-t)＋下層×t」として上記を逐次展開すると、
  最終合成比率は 標高12.5% / 曲率(紺白)12.5% / 傾斜(白茶)25% / 曲率(青赤)25% / 傾斜(白黒)25% となり、
  **FMEマニュアル別紙3の重み（標高12.5%・曲率A12.5%・傾斜A25%・曲率B25%・傾斜B25%）と数式上厳密に一致する**。
- 色の対応も確認：標高(黒⇔白)＝FME(36,36,36→246,246,246)、曲率A(紺⇔白)＝FME曲率A(42,95,131→208,223,230)、
  傾斜A(白⇔茶)＝FME傾斜A(247,213,213→134,28,33)、曲率B(青⇔赤)＝FME曲率Bの両端(50,96,207/198,72,59)、
  傾斜B(白⇔黒)＝FME傾斜B(246,246,246→36,36,36)。曲率Bの黄色中間点(255,254,190)のみ2012年論文の単純
  2色補間にはなく、後年（戸田氏解説スライド・FMEマニュアル）で追加された改良と判明。
- **結論：前回記載した「標高の重み0% vs 12.5%の食い違い」は誤り（透過率の意味の読み違い）だった。
  FMEマニュアルの5レイヤー加重合成は、戸田氏2012年原論文の合成手順を数式上厳密に再現したものであり、
  矛盾は存在しない。**FMEマニュアル準拠の方針は最上流の一次資料まで裏付けが取れたことになる。
- 参考：2012年論文の平滑化範囲の推奨値は「半径10セルの単純平均、より滑らかにしたい場合はガウス関数」。
  ArcGIS版ツール(2015年配布物)のgaus2503.txt（半径12・σ3）はこれをガウス関数化・改良した後継版と考えられる。

検証記録 2026-09-28 / CS Map Sheets v0.8.0 — XYZタイルWebP対応

対象: 引継ぎ書「CS Map Sheets QGIS WebPタイル実装」（2026-09-27付）。

一次資料確認:
- GDAL WEBPドライバ（https://gdal.org/en/stable/drivers/raster/webp.html）:
  CreateCopy対応。作成オプションLOSSLESS（既定FALSE、値はTRUE/FALSE表記）、QUALITY（既定75、
  ロスレス時の扱いは文書に明記なし＝未確認）。RGB(3バンド)/RGBA(4バンド)対応。ビルド依存
  libwebp（オプショナルドライバのためQGIS同梱GDALに含まれるかは実機確認が必要）。
- GDAL MBTilesドライバ（https://gdal.org/en/stable/drivers/raster/mbtiles.html）:
  TILE_FORMAT=WEBPはGDAL 3.8以降で対応。
- GDAL GeoPackageドライバ（https://gdal.org/en/stable/drivers/raster/gpkg.html）:
  TILE_FORMAT=WEBPはOGC採択済み拡張仕様（"WebP as extension"）として対応。独自拡張ではない。
- QGIS 3.44同梱GDALのバージョン・WEBPドライバ有無は一次資料からは断定できず、Windows実機での
  `gdalinfo --formats`確認が必要（引継ぎ書8.項目8と同じ結論）。

方針決定（2026-09-28、ユーザー指示）:
1. フォルダーXYZ z/x/y.webp の追加を第一段階とし、MBTiles/GeoPackage格納は対象クライアントとの
   互換性確認まで実装しない。
2. ロスレスWebP時のQUALITYオプション実際の挙動、QGIS同梱GDALのWEBPドライバ有無はこの開発環境
   （GDALなし）では実機検証できないため、コードは「エンコーダー未対応なら明確なエラーで停止
   （PNGへ黙って切替えない）」という要件を満たす形で実装し、実機確認はユーザー側のWindows/QGIS
   環境で行う。

実装:
- engine/xyz_tiles.py: `xyz_format`設定（png/webp、既定png）、`_tile_encoder()`関数でドライバ選択
  とドライバ不在時のRuntimeError、run.json/xyz.jsonへのformat/template/encoder記録。
- algorithm.py: `XYZ_FORMAT`パラメータ（PNG/WebP選択、既定PNG=0）を追加、config辞書へ配線。

自動テスト:
- 実行: python -m unittest discover -s csmap_sheets/tests -t . -v
- 結果: 78件中70件成功、8件スキップ（この環境にGDAL/PDAL/QGISがないため）、失敗0件。
- 追加テスト: xyz_format不正値の拒否、`_tile_encoder`のPNG/WebP選択とWEBPドライバ不在時の
  RuntimeError（モックGDAL使用、GDAL非依存で実行可能）。
- GDAL実機が必要なため本環境ではスキップしたテスト（Windows/QGIS実機で要実行）：
  WebPロスレス出力の画素完全一致（`test_rgba_webp_lossless_integration`）、
  WEBPドライバ不在時に出力フォルダーが作成されないことの確認
  （`test_webp_driver_missing_raises_and_writes_no_output`）。

未実施（次回以降）:
- Windows/QGIS 3.44でのGDAL WEBPドライバ実在確認、実際のWebP出力とPNG回帰の画素比較。
- 生成したWebP XYZをQGISおよび配信予定のWebクライアントで読み込む検証。
- MBTiles/GeoPackage格納方式の仕様確認・実装（対象クライアントとの互換性確認後に着手）。
- 公開データの既存WebPタイルの実際のエンコード方式（可逆/非可逆）の確認。

===============================================================================
v0.8.1 — 森林航空レーザ（LEM）入力「Lemが流れない」不具合の調査・修正（2026-09-28）

報告内容:
- ユーザー報告「Lemが流れない。」に続き、以下の証跡が提供された。
  1. QGIS Processingダイアログのスクリーンショット：ウィンドウタイトルに「(応答なし)」表示、
     入力形式「森林航空レーザ成果」、入力ファイル「793個の入力が選択されました」等の設定値が
     見える状態で固まっている。
  2. Windowsタスクマネージャーのスクリーンショット：qgis-ltr-binのCPU使用率8.2%・メモリ315.8MB、
     ディスク/ネットワークI/Oはほぼゼロ。
  3. Windowsエクスプローラーのスクリーンショット：デスクトップ「1mLem」フォルダーに794個の
     項目（すべて`.lem`拡張子、例：`06lc724_1g.lem`）、対応する`.csv`は目視で確認できず、
     ハング発生時刻に作成された`test`サブフォルダーが存在。
  4. 追加メッセージ「ログタブは表示できません。」（UIが完全にフリーズし、バックグラウンド処理が
     単に遅いだけの状態ではないことを示す）。

診断（確定事項・コード調査による）:
- `engine/forest_dem.py`の`expand_sources()`（修正前）は、`.lem`ファイルの同名`.csv`が
  `path.with_suffix('.csv')`で直接見つからない場合、`path.parent.glob('*')`でフォルダ内の
  全ファイルを毎回再列挙してからステム名一致を線形探索していた。
- 対応CSVを持たないLEMファイルがN件ある場合、この「フォルダ全件再列挙」がN回発生し、
  1回あたりのコストもフォルダ内ファイル数に比例するため、全体としてO(N²)のファイルシステム
  走査になる。本件のように794件全てに対応CSVが1件も無い場合、この経路が794回すべてで
  発生する最悪ケースに一致する。
- `expand_sources()`は`algorithm.py`の`prepareAlgorithm()`（`validate_config()`→`discover()`
  経由）から呼び出されており、QGIS Processingフレームワークはこの`prepareAlgorithm()`を
  バックグラウンドタスクスレッドではなくUI/メインスレッド上で実行する。このため、走査が
  終わるまでQGIS全体のイベントループが処理されず、Windowsが「応答なし」と判定し、
  ログタブへの切替えを含むあらゆるUI操作が不能になる。
- タスクマネージャーの「低いが非ゼロのCPU・ほぼゼロのディスク/ネットワークI/O」という所見は、
  真のデッドロックではなく、多数の小さなファイルシステム操作（stat呼び出し中心）を
  逐次実行している状態と整合する。

推測・未確認事項（明示）:
- 「794件全てに対応CSVが1件も存在しない」ことは、ユーザーのフォルダースクリーンショットに
  `.csv`が1件も表示されていない、という目視確認に基づく推測であり、コード側からの確証では
  ない。元データ納品ZIPや別フォルダーに`.csv`一式が存在する可能性については、この時点で
  ユーザーへ確認を依頼中であり、本ファイル作成時点で回答は得られていない。
- QUALITY/WEBPドライバの実機挙動とは無関係の、別系統の不具合である。

対応方針:
- 性能不具合として確定次第、ユーザーの回答を待たずに修正を実施（データが本当に存在しない場合でも
  「フリーズ」ではなく「速やかな明確エラー」にすることは、回答内容に関わらず正しい修正のため）。
- `.lem`に`.csv`が必須という仕様自体（`classify_sources()`の`ValueError`）は変更しない。
  引継ぎ内容にある「フォーマット要件」であり、性能不具合の修正対象ではない。

実装:
- `engine/forest_dem.py`: `expand_sources()`に`csv_index_cache`（ディレクトリ→{ステム小文字:Path}
  の辞書）を追加。ディレクトリごとに`directory.glob('*')`を1回だけ実行し、`suffix.lower()=='.csv'`
  で絞り込んでインデックス化することで、修正前と同じ「拡張子は大文字小文字を区別しない」挙動を
  保ったままO(N²)をO(N)＋ユニークディレクトリ数分の列挙へ改善。

自動テスト:
- 実行: python3 -m unittest discover -s csmap_sheets/tests -t .
- 結果: 80件中72件成功、8件スキップ（この環境にGDAL/PDAL/QGISがないため）、失敗0件。
  （v0.8.0時点の78件から、本修正の回帰テスト2件を追加して80件）
- 追加テスト（`tests/test_inputs.py`）:
  1. `test_forest_expand_sources_scans_each_directory_once`：対応CSVを持たないLEMファイル
     50件を用意し、`Path.glob`をパッチしてカウント。同一ディレクトリに対する`glob('*')`が
     1回だけであることを検証（＝O(N²)への回帰防止）。展開結果が入力LEM数のまま
     （companionが追加されない＝対応CSVなしと正しく判定）であることも確認。
  2. `test_forest_expand_sources_finds_case_insensitive_companion_via_cache`：ステムの
     大文字小文字が異なる`.csv`（例：`A.CSV`に対する`a.lem`）でも、キャッシュ経由で
     修正前と同じく対応付けできることを検証。

未実施（次回以降）:
- ユーザー環境（Windows/QGIS 3.44、794件のLEMフォルダー）での実機再現確認・修正後の応答性確認。
- `.csv`メタデータの所在確認（ユーザー回答待ち）。所在が判明した場合、正しい入力フォルダーで
  再実行して実際のCS立体図生成まで進むかの確認。
- 大量ファイル（数百～数千件規模）を扱う他の入力経路（XYZ/CSV格子、TIFF+ワールドファイル等）に
  同種のO(N²)パターンが無いかの横展開確認（本件はLEM経路のみを修正・確認した）。

===============================================================================
v0.9.0 — WebP非可逆(lossy)出力の追加（2026-09-28）

ユーザー要望:
「webpですが、非可逆を求められました。非可逆webpを選択できるようにしてください。非可逆の場合の
仕様は、以前議論した通りです。」

以前議論・確認済みの仕様（本ファイルv0.8.0セクション参照、gdal.org WEBPドライバページで確認）:
- QUALITY: 1-100、GDAL WEBPドライバの既定値75。
- LOSSLESS: TRUE/FALSE、GDAL WEBPドライバの既定値FALSE。
- QUALITY/LOSSLESSともCreateCopy作成オプションとして指定する。

実装:
- engine/xyz_tiles.py: DEFAULTSに`xyz_webp_lossless`(既定True＝従来どおり可逆)と
  `xyz_webp_quality`(既定75)を追加。`validate_xyz()`で型・範囲(1-100の整数)を検証。
  `_tile_encoder()`は`webp_lossless`/`webp_quality`引数を追加し、可逆時は従来どおり
  `LOSSLESS=TRUE`、非可逆時は`LOSSLESS=FALSE`と`QUALITY=<値>`を明示的に作成オプションへ渡す。
  WEBPドライバ不在時のRuntimeErrorは可逆・非可逆どちらの設定でも同様に発生する（フェイルファスト
  方針は維持）。
- algorithm.py: `XYZ_WEBP_LOSSLESS`（Boolean、既定True、Advanced）・`XYZ_WEBP_QUALITY`
  （Integer 1-100、既定75、Advanced）パラメータを追加し、`prepareAlgorithm()`で
  `xyz_webp_lossless`/`xyz_webp_quality`として設定辞書へ配線。shortHelpStringを更新。

自動テスト:
- 実行: python3 -m unittest discover -s csmap_sheets/tests -t .
- 結果: 82件中73件成功、9件スキップ（この環境にGDAL/PDAL/QGISがないため）、失敗0件。
- 追加テスト（tests/test_xyz.py）:
  - `test_settings`: `xyz_webp_lossless`/`xyz_webp_quality`の不正値（非bool、範囲外、float）の
    拒否を追加。
  - `test_webp_defaults_are_lossless_quality75`: 既定値が可逆・QUALITY75であることを確認
    （後方互換：v0.8.0時点の既定動作を変えていないことの確認）。
  - `test_encoder_selection_and_missing_driver_error`: 非可逆選択時に`LOSSLESS=FALSE`・
    `QUALITY=40`が作成オプションに含まれること、ドライバ不在時は可逆・非可逆いずれでも
    RuntimeErrorになることを確認（モックGDAL、GDAL非依存で実行可能）。
  - `test_rgba_webp_lossy_integration`（GDAL実機必要、この環境ではスキップ）: 非可逆WebP出力が
    完了し、`encoder.lossless=False`・`encoder.quality=40`が記録されること、単色領域の復元誤差が
    許容範囲内（±20）であることを確認。可逆時のテストと異なり画素完全一致は求めていない
    （非可逆圧縮の性質上、要件でもない）。

未実施（次回以降）:
- Windows/QGIS実機でのWEBPドライバ実在確認と、実際の非可逆WebP出力の目視・ファイルサイズ確認。
- 非可逆時のアルファチャンネルの扱い（libwebpの既定でアルファが可逆圧縮されるかどうか）の実機確認。
- 配信先クライアント側での非可逆WebPタイルの表示互換性確認。

===============================================================================
v0.9.0 — 実機（Windows/QGIS）検証：WEBPドライバ実在・可逆WebP出力（2026-09-29）

ユーザーから、実機（Windows/QGIS 3.44、公開データ・甲府エリアで検証、入力形式「標高ラスタ
（GeoTIFF）」）でプラグインv0.9.0を実行した処理結果一式（run.json・図郭GeoTIFF・XYZタイル等）
2件（セルサイズ1m版・0.5m版、それぞれ同一範囲・同一設定でセルサイズのみ変更）を受領し、
Claude側で解析した。

検証に使用したデータの出典:
- 山梨県点群データ（航空LP・MMS）（山梨県 新価値創造推進局 新事業チャレンジ推進課）を加工して
  検証に使用した。
  - 公開元：G空間情報センター（https://www.geospatial.jp/ckan/dataset/yamanashi-pointcloud-2024）
  - 使用したリソース：グリッドデータ_DEM（航空LP 0.50m、GeoTIFF形式）
  - ライセンス：CC BY 4.0/ODbL のデュアルライセンス。利用条件は、同ページの「利用規約及び免責事項」による。
- 出典：国土地理院 基盤地図情報 数値標高モデル（1mメッシュ、DEM1A）
  - 基盤地図情報 数値標高モデル（国土地理院）を加工して検証に使用した。
- 加工したのは本ソフトの開発者。加工の内容は次のとおり。
  - 山梨県点群データのDEM：元の平面直角座標系（第VIII系）のまま、セルサイズの調整（再標本化）と
    CS立体図の計算を行った。座標系の変換は行っていない。
  - 基盤地図情報 数値標高モデル：GML形式から格子データへの変換、緯度経度から平面直角座標系への
    座標変換、CS立体図の計算を行った。
- 本ソフトの出力と検証結果は、各提供元が作成したものではなく、提供元による保証もない。また、
  提供元のデータそのもの（未加工のデータ）でもない。

確認できた事実（run.json・実タイルファイルの解析による）:
1. QGIS 3.44同梱GDALにWEBPドライバが実在する。run.jsonの`xyz.encoder.driver`が`"WEBP"`で
   記録されており、実際にWEBP形式でのCreateCopyが成功している
   （v0.8.0/v0.9.0セクションに記載していた「未確認事項」のうち1点が解消）。
2. 可逆(lossless)WebP出力が実機で正常動作する。両ZIPとも`run.json`の`status`が`"completed"`、
   `xyz.status`も`"completed"`で、657枚のXYZタイル（候補4467枚中、透明タイル3810枚を正しく
   スキップ）を出力。`xyz.encoder`は`{"driver":"WEBP","lossless":true,"quality":null,
   "creation_options":["LOSSLESS=TRUE"]}`で、意図した作成オプションどおり。
3. `cell_size`（1m/0.5m）を変えても計算結果は整合的。`valid_relief_pixels`が1m版
   5,695,591px・0.5m版22,828,865pxで、比率は約4.01倍（セルサイズを半分にすると各辺の画素数が
   倍になり面積で4倍になる、という期待どおりの挙動）。図郭数（4枚）・XYZタイル数（657枚、
   同一地理範囲のため一致）も両版で一致しており、cell_size周りの計算に不整合は見られない。
4. WebPタイルを実際にPillowでデコードして確認したところ、657枚中333枚がRGB（3バンド）、
   324枚がRGBA（4バンド、アルファは0/255の二値）だった。原因を調査した結果、これはバグでは
   なく、透明画素を一切含まない（図郭内部の）完全不透明タイルではlibwebp/GDALがアルファ
   チャンネルを自動省略する、正当なロスレス最適化の挙動と判断する（推測ではなく、対象タイルの
   デコード結果で実際に確認した事実）。図郭境界にかかる縁のタイルのみアルファチャンネルを
   保持していた。ただし、PNG出力（GDAL PNGドライバは通常、不透明でも4バンド固定で書き出す）
   とはバンド数が図郭内で不揃いになる点で挙動が異なるため、配信先クライアントが「同一フォル
   ダー内で全タイルのバンド数が同じ」ことを前提にしている場合は影響し得る。この点はPNG側の
   挙動を含めてWindows実機での比較確認までは未確認（推測に基づく注意喚起）。

まだ未確認（次回以降、ユーザーにより2026-09-30以降に実施予定）:
- 非可逆(lossy)WebP出力の実機確認（QUALITY指定時の実際のファイルサイズ・画質・アルファ
  チャンネルの扱い）。
- 森林航空レーザ成果（LEM）入力・対応CSVを含む大量ファイル（数百件規模）でのv0.8.1の
  応答なし修正の実機確認。
- PNG出力とWebP出力とでタイルのバンド数（RGB/RGBA）の扱いに差があるかどうかの実機比較。

自動テスト: 変更なし（ドキュメントのみの更新）。

===============================================================================
v0.9.1 — LEM対応メタデータの.txt拡張子対応（2026-09-30）

ユーザーから、実機（Windows/QGIS 3.44.1.13 LTR）で森林航空レーザ成果（LEM）入力を実行した際の
スクリーンショット6枚を受領。以下2点が確認された。

1. 入力フォルダー内の `glid_1m/csv形式/06je983_1g.txt` を開けなかったというOGR警告。
   森林航空レーザ成果のデータでは、LEMの対応メタデータファイルが、規則上は
   `.csv`拡張子のはずだが実態として`.txt`拡張子で提供される場合が多いことが判明した
   （ユーザー確認済み事実）。
2. `processing/gui/AlgorithmDialog.py`の`asMap(parameters, self.context)`呼び出し箇所で
   `Exception: unknown`という未処理例外。QGIS本体側のパラメータ収集フェーズで発生しており、
   `csmap_sheets`の`prepareAlgorithm()`より前の段階と見られる。トレースバック全文は未取得
   （画面に3行のみ表示、切り分けに必要な情報が不足）。この例外と上記1.のOGR警告が同一原因
   かどうかは未確認（asMap()はパラメータのシリアライズであり、通常はファイル読み込みより
   前に走るため、無関係の可能性もある）。

対応（1.のみ、2.は原因未特定のため見送り）:
- `engine/forest_dem.py`の`expand_sources()`/`classify_sources()`を変更し、LEMの対応
  メタデータファイル探索を`.csv`固定から`.csv`/`.txt`両対応に拡張。判定自体は元々
  `is_lem_metadata()`による中身のCSV構造チェック（拡張子非依存）のため、探索対象の拡張子を
  増やすだけで対応できた。同一ファイル名(stem)に`.csv`と`.txt`が両方存在する場合は`.csv`を
  優先する（仮定：現場データはどちらか一方のみのはずで、優先順位が実害になるケースは
  想定していない。異なる優先順位が必要な場合は要修正）。
- `.txt`は単体のXYZ/CSVグリッド入力としても使われる拡張子のため、LEM companion扱いは
  「同名`.lem`が存在し、かつ内容がLEMメタデータ構造に一致する場合のみ」に限定し、通常の
  XYZグリッド用`.txt`の分類には影響しない設計とした。
- 対応する`.csv`/`.txt`のいずれも見つからない場合は、従来どおり明確なエラーで即座に停止する
  （黙ったフォールバックは行わない）。

自動テスト: `tests/test_inputs.py`に3件追加（`.txt`companionのみでLEM分類が成功すること、
`.csv`/`.txt`同一stemで`.csv`が優先されること、単体`.txt`グリッドの分類が従来どおり`grid`の
ままであることの回帰防止）。全85件（うち実機依存9件スキップ）でリグレッションなしを確認済み
（この開発サンドボックス環境、GDAL/QGIS実機は使用せず）。

まだ未確認:
- 本修正がWindows/QGIS実機の検証用データ（`glid_1m/csv形式/`）で実際にLEM入力を完走させられるか。
- 上記2.のPython例外（`AlgorithmDialog.asMap()`内`Exception: unknown`）の根本原因。
  トレースバック全文の取得と、入力設定（森林航空レーザ成果の指定パス、選択ファイル/フォルダー
  の詳細）の確認が必要。1.の`.txt`対応とは無関係である可能性が高いが未確定。

===============================================================================
v0.9.2 — v0.9.1の.txt対応が招いたQGIS「応答なし」ハングの修正（重大・回帰、2026-09-30）

**これは私（開発側）の実装ミスによる回帰バグであり、QGIS本体側の不具合ではない。**

ユーザーが実機（Windows/QGIS 3.44.1.13 LTR）で`hyogo_lem_1m`データセットを用いて追加検証を
実施。以下の経過で原因を特定した。

1. `hyogo_lem_1m/csv形式/06je983_1g.txt`を対象に実行すると、v0.9.1のCHANGELOGに記載した
   OGR警告・`AlgorithmDialog.asMap()`の`Exception: unknown`が再現した。
2. ユーザーの提案（方法A：個別ファイル選択ではなく「入力フォルダー」指定）で切り分けを試みたが、
   `INPUT_FOLDER`（`QgsProcessingParameterFile`, Folder）経由でも同じOGR警告が再発し、当初の
   仮説（`QgsProcessingParameterMultipleLayers`の個別ファイルのレイヤー解決失敗）は誤りと判明
   （ユーザーへの訂正報告済み）。
3. さらに実行を続けると、例外ではなくQGISのタイトルバーが「(応答なし)」表示となり、タスク
   マネージャーでCPU使用率約1%・メモリ336MB程度のまま長時間停止する状態を確認（ユーザー
   確認済み事実。複数回再現）。
4. ユーザーがWindowsエクスプローラーのフォルダー構成を確認・共有した結果、
   入力フォルダー（`hyogo_lem_1m/`）直下に以下2つのフォルダーが存在することが判明
   （ユーザー確認済み事実）：
   - `lem形式/`：1,428件（`.csv`+`.lem`ペア714組。`.csv`は約35KB、`.lem`は約14MB）。
   - `txt形式/`：714件、すべて`.txt`。`.lem`ペアは一切存在しない。サイズは16KB〜約97MB
     （例：`06je983_1g.txt`が約20,930KB）。つまりこれらは、v0.9.1が「LEM対応メタデータ
     companionかもしれない」として中身を検査していた大容量の単体テキストグリッドだった。

根本原因（自己特定、高確度）：v0.9.1で追加した`is_lem_metadata()`の判定対象拡張子`.txt`拡張は、
「中身のCSV構造で判定するので安全」という設計判断だったが、判定のために`read_lem_metadata()`
（実体は`Path.read_bytes()`によるファイル全体読み込み）を経由して**ファイル全体**を読んで
いた点を見落としていた。`txt形式`フォルダーのように、大容量（最大約97MB）の`.txt`が714件
存在する検証用データでは、`classify_sources()`がそれぞれに対して全文読み込みを行い、I/O・
メモリ確保の累積負荷によってQGISが長時間「応答なし」になっていた。CPU使用率がほぼ0%だった
観測は、CPUバウンドの計算ではなくI/O待ち・メモリ確保待ちの停止と整合する（仮定：プロセス
全体のプロファイリングは行っておらず、タスクマネージャーの概況観測のみに基づく推測）。

修正（`engine/forest_dem.py`）:
- `LEM_METADATA_PROBE_BYTES = 262144`（256KiB）を新設。
- `is_lem_metadata()`を、ファイル先頭の`LEM_METADATA_PROBE_BYTES`バイトのみを読む
  `_decode_head()`経由の判定に変更（末尾が読み込み境界で途切れた不完全な行は破棄してから
  デコードし、マルチバイト文字の途中切断・不完全な行によるパース誤りを防止）。
- 実測した本物のLEM対応メタデータCSV（`hyogo_lem_1m/lem形式/`内で約35KB）に対し、256KiBは
  約7倍の安全マージンを持つ（仮定：将来、35KBを大きく超える本物メタデータが出現した場合は
  誤ってgrid扱いされる未確認の残存リスクがあるが、巨大`.txt`によるハング再発を防ぐことを
  優先した設計判断）。
- ファイル全体を読む`read_lem_metadata()`自体は変更せず、`is_lem_metadata()`で候補と確認
  された後にのみ呼び出す用途であることをdocstringで明記した。

自動テスト: `tests/test_inputs.py`に3件追加。
- `test_is_lem_metadata_does_not_read_past_probe_bound_on_large_grid_txt`：
  `LEM_METADATA_PROBE_BYTES`の8倍サイズのスパースファイル（実容量を消費せず`seek`+1バイト
  書き込みで巨大サイズのみ再現）に対し、`builtins.open`をモックして`read()`に渡される
  サイズが常にプローブ上限以下であることを検証。
- `test_is_lem_metadata_accepts_real_size_companion_beyond_naive_estimate`：実測に近い
  約35KB（30,000バイト超）のメタデータファイルがプローブ上限内で正しく認識されることを
  検証（初回実装時、水増しデータの行数不足で実際には18,927バイトしか生成できておらず
  アサーションが失敗するテスト自体の不具合があったため、行数を増やして修正済み）。
- `test_forest_classify_sources_large_standalone_txt_grid_is_not_metadata`：
  `LEM_METADATA_PROBE_BYTES`の4倍サイズの単体`.txt`（`.lem`ペアなし）が、全文読み込み
  なしで正しく`grid`として分類されることを検証。
全88件（うち実機依存9件スキップ）でリグレッションなしを確認済み（開発サンドボックス環境、
GDAL/QGIS実機は未使用）。

まだ未確認:
- 本修正がWindows/QGIS実機の`hyogo_lem_1m`（`lem形式`・`txt形式`双方）で実際にハングせず
  完走するか。ユーザーによる再検証待ち。
- v0.9.1のCHANGELOGに記載した`AlgorithmDialog.asMap()`内`Exception: unknown`が、本ハングと
  同一原因か、独立した別問題かは依然未確認。トレースバック全文（画面の「スタックトレース」
  ボタン押下で表示される内容）は未取得のまま。本修正の適用後もこの例外が再発する場合は、
  改めてトレースバック全文の提供を依頼する必要がある。
- 本修正版（v0.9.2）が、ユーザーに既に提供済みのv0.9.1のZIPを置き換えるものであることを
  明確に伝達済みか（本バリデーションログ作成時点ではまだユーザーへの再配布前）。

v0.9.3 — 個別ファイル選択のQGIS長時間ハング対策・入力操作性改善・結合GeoTIFF・出力CRS自動推定（2026-09-30）

背景：ユーザーが実機（Windows/QGIS 3.44.1.13 LTR）でv0.9.2運用中に複数の問題を報告。

1. `FILES`（個別ファイル選択）パラメータが、公式の「入力パラメータ」ログ上は`None`と
   表示されるのに、`QgsProcessingParameters.parameterAsFileList()`が全く無関係な
   Windows特殊フォルダー（`C:\Users\33946\Documents\My Music`）を返し、
   `[WinError 5] アクセスが拒否されました`で即座に失敗する事象を2回再現（ユーザー確認済み
   事実）。既存コード中に「QGIS 3.44でこの種の不整合が観測された」という先行コメントが
   あったが、詳しい経緯は現行コードから追跡できず未確認のまま。
2. ユーザー独自の監視ツール「MORIZON」のログで、UIスレッドが3395.5秒（約56分）応答して
   いなかったことを確認（ユーザー確認済み事実）。実行後にのみ発生し、ファイル選択中には
   発生しないことをユーザーが確認済み。これはQGIS Processingフレームワーク自身が
   `QgsProcessingParameterMultipleLayers`（`FILES`）の個別選択件数分だけレイヤー解決を
   試みる挙動によるものと推定（仮定：QGIS本体のソースは未調査で、観測される症状からの
   推定に留まる。本プラグインのPythonコードでは直接の制御が及ばない領域）。
3. フォルダー選択時は`.tfw`等の同梱物拡張子が黙ってスキップされるのに、個別ファイル選択時
   だけ`Input extension does not match input_type`で強制停止し、後から`.tfw`だけを選択から
   外す操作がQGIS標準のファイル選択チェックボックス画面上で非常に煩雑という報告（ユーザー
   確認済みの実運用上の困難）。
4. 「マージしたGeoTIFF出力が欲しい」「出力のCRSはデフォルトで入力と同じにしてほしい」という
   新規機能要望2件。

対応（ユーザー承認済み、「では推奨案を進めましょう。」2026-09-30）：

(1) `algorithm.py`の`prepareAlgorithm()`から、`QgsProcessingParameters.parameterAsFileList()`
    へのフォールバックを完全に削除。生の`parameters.get('FILES')`のみを信頼する。実測では
    異常時・正常時の両方でraw_filesが実態と一致していたことを根拠とする。
    未確認の残存リスク：このフォールバックは元々別の不具合（詳細不明）への対策として
    追加されていた可能性があり、今回削除したことで将来別パターンの不整合が再発する
    可能性は否定できない。

(2) `algorithm.py`の`prepareAlgorithm()`に、個別ファイル選択（`FILES`）件数の上限チェックを
    追加（`INDIVIDUAL_FILE_SELECTION_LIMIT = 100`、暫定値）。上限超過時は処理を開始せず、
    「入力フォルダー」の使用を案内する明確なエラーで停止する。上記2.の根本原因（QGIS自身の
    挙動）はPythonコード側で直接修正できないため、事前検知による回避を選んだ。閾値100は
    実運用データでの検証は未実施の暫定値であり、将来的な再調整を想定する。

(3) `engine/input_sources.py`の`discover()`に`COMPANION_ONLY_EXTENSIONS = {'.tfw', '.tifw',
    '.wld'}`を新設。個別ファイル選択でもフォルダー選択と同様、これらの拡張子を黙って
    スキップするよう統一（フォルダー選択と個別選択とで挙動が異なる非対称性を解消）。
    ただし全entryを通して有効な入力が1件も無い場合（例：`.tfw`のみを個別選択した場合）は
    従来どおり明確なエラーで停止し、「入力0件」を黙って通過させないことを確認。
    回帰テスト3件追加（`tests/test_inputs.py`）。

(4) `engine/pipeline.py`の`run()`に、既存の全域CS方式画像（`cs_relief.vrt`、図郭タイルの
    モザイクVRT）を`gdal.Translate()`で単体の`cs_relief_merged.tif`としても書き出す処理を
    追加。新規ブーリアンパラメータ`MERGED_GEOTIFF`（既定True、ユーザーが「推奨」として
    選択）で有効/無効を切替可能。新規出力`CS_MERGED_GEOTIFF`を追加（自動読み込み対象には
    含めない：既存の`CS_IMAGE`(VRT)と内容が重複するマップレイヤーの二重読み込みを避ける
    ための実装判断、ユーザーへの明示確認はしていない軽微な仮定）。
    回帰テスト1件追加（`tests/test_pipeline.py`、`merged_geotiff_enabled`の既定値・
    型検証）。

(5) 出力CRS自動推定：ユーザーが選択した「画面の初期値だけ、入力から推定した系番号にして
    ほしい（推奨）」は、QGIS標準の宣言的パラメータAPIでは画面の初期値自体を選択中の入力に
    リアルタイムに反応させることができない（カスタムウィジェットラッパーの実装が必要で
    過剰な工数）ことをユーザーに説明し、代替として「CRSパラメータを省略可とし、空欄なら
    実行時に先頭の入力ラスターファイルの実際のCRSから平面直角座標系第I～XIX系のいずれかを
    自動推定する」方式を提案、ユーザーが承認（AskUserQuestion、2026-09-30）。
    - `engine/pipeline.py`の`check_sources()`内にあった系番号照合ロジック（原点緯度経度・
      縮尺係数・偽東距/偽北距による19系との照合）を`_jpr_zone_of(srs)`として関数化し、
      新設の`infer_target_crs_from_raster(path, gdal, feedback)`と共有。既存の
      `check_sources()`の挙動は変更していない（リファクタリングのみ、意味的な変更なし）。
    - `algorithm.py`の`CRS`パラメータを`optional=True`（既定値なし）に変更。空欄の場合、
      ラスター入力（`mode=='raster'`）のみ、先頭ファイルのCRSを自動推定。一致しない場合・
      ラスター以外の入力種別の場合は、黙ったフォールバックをせず明確なエラーで停止し、
      CRSの明示選択を案内する。
    - 未確認事項：本開発環境にはosgeo(GDAL/OSR) Pythonバインディングが入っておらず、
      `_jpr_zone_of()`/`infer_target_crs_from_raster()`はosr.SpatialReferenceの
      インターフェースを模したスタブオブジェクトによる純粋な数値照合ロジックの検証に
      留まる（`tests/test_pipeline.py`の`JPRZoneDetectionTests`、5件追加）。実際の
      GDAL/OSRオブジェクトに対する動作、および実機QGIS環境での自動推定の動作は
      未検証。ユーザーの実機での再検証を強く推奨する。

自動テスト：`python -m unittest discover -s csmap_sheets/tests -v` → 96件中87件成功、
9件スキップ（実機依存：GDAL/PDAL/QGIS不在）、失敗0件。python構文チェック
（`python -m py_compile algorithm.py engine/pipeline.py engine/input_sources.py`）成功。

まだ未確認・未実施：
- Windows/QGIS実機での動作確認全般（本ラウンドの5項目いずれも実機未検証）。
- METRESチェックボックス（`confirm_elevation_metres`）にチェックを入れて再実行した際に
  ユーザーが新たに報告したエラーの内容（本バリデーションログ作成時点で、旧ログ
  （`My Music`/`WinError 5`）の再掲か、真に新規のエラーかが未確定。旧v0.9.2ビルドの
  まま再実行された可能性が高く、本ラウンドの(1)修正が未適用の状態での再現である疑いが
  ある。ユーザーに最新ログの提供を依頼中）。
- (2)の閾値100件、(4)の自動読み込み対象外という実装判断は、いずれもユーザーへの
  明示確認なしの妥当な仮定であり、実運用状況次第で見直しが必要になる可能性がある。

v0.9.4 — XYZ最大ズーム標準値見直し・候補タイル数超過の早期検出・CRS自動推定エラー
メッセージ改善（2026-09-30）

背景：v0.9.3を実機（京都DEMの検証用データ、195件のLEMファイル、1066候補図郭）でテストした
ユーザーから、以下2点の実測報告があった。
- CS立体図計算(render_sheets)中、QGISのUIスレッドが最大1208秒(約20分)応答していない
  ように見える期間がMORIZON（ユーザー所有の別プラグインに実装された、Qtの250ms
  タイマーで主スレッドの応答性を実測する診断ウォッチドッグ）で記録された。ソース確認の
  結果、この監視はプラグイン読み込み時に自動起動し常駐する設計で、ユーザーの手動操作
  とは無関係に主スレッドの応答性を実測していることを確認。GDAL Pythonバインディング
  (osgeo)の多くのラスターI/O呼び出しがPythonのGIL(Global Interpreter Lock)を長時間
  解放しない既知の制約により、`processAlgorithm()`がQGISの設計通り別スレッド
  (`QgsTask`)で実行されていても、その中の重いGDAL呼び出しがGILを握り続けると主スレッド
  側が見かけ上「応答なし」になり得るという解釈で説明。処理自体は最終的に正常完了して
  おり(2時間15分3秒中の1時間32分41秒でCOMPLETED)、csmap_sheets固有の不具合という
  確証は得られていない（未確認、GDALバインディングのGIL解放挙動はバージョン依存で
  実測していない）。今回のコード修正はこの点には対応していない。
- 同じ実行で、CS立体図計算・結合GeoTIFF書き出しが完了した**後**（2時間15分3秒経過後）に
  なって、XYZタイルの候補数(551,912枚)が上限(`xyz_max_tiles`既定100,000枚)を超過した
  というエラーで処理全体が失敗した。ガード自体は仕様通りに機能したが、検出タイミングが
  最重量の計算の後という設計上の欠陥（フェイルファスト方針からの逸脱）をユーザーに
  指摘し、(1)標準ズームレベルの引き下げ、(2)早期検出の実装、の2点を提案。ユーザーが
  両方を承認し、(2)の実装にあたり「上限を増やせるならそれも勘案」との指示を受けた
  （現状`XYZ_LIMIT`パラメータで既に上限10,000,000まで手動調整可能であり、この要望は
  早期検出の実装そのもの、および失敗時メッセージが増枠を案内する形（既存の
  "increase xyz_max_tiles"文言）で満たされると判断。標準値そのものの引き上げは、
  むやみに引き上げると暴走的なタイル生成の検知が弱まるため見送った：判断根拠として
  明示していない仮定）。

(1) `algorithm.py`の`XYZ_MAX`パラメータ既定値、および`engine/xyz_tiles.py`の
    `DEFAULTS['xyz_max_zoom']`を18から16へ変更。

(2) `engine/xyz_tiles.py`に`raster_bounds_3857()`（任意の測地参照ラスターの範囲を
    EPSG:3857へ変換、`write_xyz()`のRGBA/Byte検証とは独立）、`estimate_candidate_tiles()`
    （範囲・ズーム範囲から候補タイル数を計算、`write_xyz()`と同じ計算式）、
    `precheck_xyz_tile_count()`（上記2つを組み合わせ、上限超過なら`ValueError`）を新設。
    `engine/pipeline.py`の`run()`で、投影済みDEM(`projected_dem.vrt`)を対象に
    `render_sheets()`を呼ぶ**前**にこの事前検証を実行するよう変更。
    - 設計上の注意点（未確認事項）：事前見積りは投影済みDEMの範囲を使うが、実際の
      CS立体図(`cs_relief.vrt`)の範囲は`render_sheets()`が図郭境界に合わせてわずかに
      拡張する場合があり、両者は厳密には一致しない。そのため事前見積りは実際の候補数を
      わずかに下回る可能性があり、「事前検証は通過したがXYZ生成本番でやはり超過する」
      ケースは理論上あり得る。この場合も`write_xyz()`内の従来からの本チェックが最終的な
      安全網として機能するため、安全性そのものは損なわれない（二重チェック設計）。
    - `write_xyz()`自体の候補数計算ロジックは変更していない（意図的にリスクを避けた：
      既存のテスト済みコードパスに触れず、新規追加のみで対応）。

(3) `engine/pipeline.py`の`infer_target_crs_from_raster()`の戻り値を、`wkt`単体から
    `(wkt, reason)`のタプルへ変更。CRS情報が全く無い場合（TIFF＋TFWワールドファイル等）
    と、CRSは定義されているが平面直角座標系のいずれとも一致しない場合とで、`reason`の
    文言を区別。`algorithm.py`のCRS自動推定失敗時のエラーメッセージに`reason`を含めて
    表示するよう変更。

回帰テスト追加（`tests/test_xyz.py`）：
- `test_estimate_candidate_tiles_matches_manual_sum`：純粋な数式ロジックの検証（GDAL不要）。
- `test_default_max_zoom_is_16`：既定値の回帰防止。
- `test_precheck_xyz_tile_count_integration`：実GDALを使った統合テスト（範囲内で通過、
  上限超過で例外、`xyz_enabled=False`で範囲を開かずNoneを返す、の3ケース）。

自動テスト：`python -m unittest discover -s csmap_sheets/tests -t .` → 99件中89件成功、
10件スキップ（実機依存：GDAL/PDAL/QGIS不在。うち`test_precheck_xyz_tile_count_integration`
は本ラウンドの新規追加分）、失敗0件。

まだ未確認・未実施：
- Windows/QGIS実機での動作確認全般（本ラウンドの3項目いずれも実機未検証）。
- `test_precheck_xyz_tile_count_integration`は本開発環境にosgeoが無いためスキップされた
  ままで、実GDAL環境での動作は未検証。
- MORIZONウォッチドッグが記録した1208秒スタッブについて、GDAL PythonバインディングのGIL
  解放制約という説明はコード読解に基づく推測であり、実測（CPU使用率の確認等）による
  裏付けは取れていない。この点への対応（コード修正）は今回見送っている。
- 標準ズーム16・上限100,000枚のままでも、対象範囲が十分広い場合（都道府県全域など）は
  再び超過する可能性がある。その場合はユーザーが`XYZ_LIMIT`を手動で引き上げる運用を
  想定しているが、これはユーザーへの明示確認を経ていない運用上の仮定である。

===============================================================================
v0.10.0 — 傾斜計算方式の選択（既定をHorn法に変更）（2026-10-02）

背景（ユーザー決定）:
傾斜の計算方式を中央差分法とHorn法から選べるようにし、既定はHorn法とする。選んだ方式は
独自方式・FMEマニュアル方式のどちらにも、組込み設定プロファイルにも使う。

実装:
(1) `engine/pipeline.py`：`SLOPE_ALGORITHMS = ('horn', 'central_difference')`と
    `slope_gradients(raw, cell, slope_algorithm='horn')`を追加。`relief()`はこれを使う。
    - `central_difference`：`dx=(E-W)/(2*cell)`、`dy=(S-N)/(2*cell)`。v0.9.4までの式と同一。
    - `horn`：`dx=((NE+2E+SE)-(NW+2W+SW))/(8*cell)`、`dy=((SW+2S+SE)-(NW+2N+NE))/(8*cell)`。
    - どちらも平滑化前のDEMを使う。必要な近傍は1セルで、既存の処理余白
      （`ceil(4*sigma_m/cell)+1 >= 1`）に収まる。符号は`np.hypot`で消えるため結果に影響しない。
(2) `validate_config()`：既定`slope_algorithm='horn'`、不正値はエラー。
    `rendering_settings()`：`terrain_calculation`に`slope_algorithm`と方式の説明を記録。
    `run.json`の`settings`とGeoTIFFの`SETTINGS`メタデータには設定JSONごと記録される。
(3) `algorithm.py`：詳細パラメータ`SLOPE_ALGORITHM`（既定Horn法）を追加。組込みプロファイルは
    この選択を上書きしない。`slope_algorithm`を持たない外部JSONプロファイルでは画面の選択が
    使われ、その旨と中央差分法で再現できることを警告としてログに出す。パラメータは既定動作の変更に
    気づけるよう通常の項目として表示する。開始時にログへ「傾斜計算方式: Horn法／中央差分法」を出力する。

PSS版との関係:
PSS版（FMEワークスペース）の`RasterSlopeCalculator`は`ALGORITHM="Horn"`を選んでいるが、
欠測付近の補間（Interpolate Nodata）、外縁の扱い、平滑化カーネルの条件が本プラグインと
異なる。そのため、Horn法にしても画素単位の完全一致は目標にしない。差を調べる場合は、同じDEM・
同じ格子で数値と画像を比較する必要がある。

検証（ユニットテスト）:
- 一定標高面で両方式とも0度。平面勾配で両方式とも解析解`degrees(atan(hypot(gx,gy)))`に一致。
- 非線形の3×3固定配列（1 2 4／3 5 8／6 9 13）で、Horn法が手計算値（dz/dx=2.5、dz/dy=3.5）に一致。
- 中央差分法の結果が、v0.9.4までの式と完全一致。既存の回帰ハッシュ
  （`32448c448f375a16ac6a29321204af8540fd86e5f9ae566b985004e96199227b`）は中央差分法を
  明示指定して維持。
- Horn法で、欠測画素の8近傍（斜め方向を含む）とラスター端部が透過される。
- 全体と部分（処理余白付き）で計算した結果が、有効範囲で完全一致（継ぎ目なし）。
- 孤立した1セルの異常値に隣接する画素で、中央差分法のdx=25.0に対しHorn法は12.5。
- 既定値・不正値の拒否・記録内容（`terrain_calculation.slope_algorithm`）。

テスト結果（GDAL/PDAL/QGISなし、Python 3.12、numpy・scipy）:
`python -m unittest discover -s csmap_sheets/tests -t .` → 121件実行、失敗0、スキップ13
（スキップ分は環境依存で未実行）。

まだ未確認・未実施:
- QGIS 3.44・Windows実機での動作（画面からの選択、ログ表示、出力への反映）。
- 旧バージョンで保存した外部JSONプロファイルの実機での読込み。
- FME出力・PSS版出力との画素比較。

===============================================================================
v0.10.1 — 版の表示、XYZ CLIの既定ズームの修正、文書の整合（2026-10-03）

内容:
- 版の表示：`engine/pipeline.py`に`version_banner()`を追加し、処理画面の説明欄の先頭と
  実行ログの最初の行に「図郭対応CS立体図作成プラグイン（CS Map Sheets） v0.10.1」を表示する。
- XYZ CLI：`build_cli_parser()`の既定値を`DEFAULTS`から取り、`--max-zoom`の既定を本体と同じ16にした。
- 文書：README_jaのZIP名と座標系の手順の修正、検証データの出典、「実データ」の表記整理。
- 計算処理（CS立体図・図郭・XYZタイル）の変更はない。

検証（ユニットテスト）:
- `VERSION`と`metadata.txt`の`version=`が一致すること、版表示に正式名称と版が含まれること。
- CLIの既定値が`DEFAULTS`と一致し、明示指定が優先され、不正な`--format`が拒否されること。

テスト結果（GDAL/PDAL/QGISなし、Python 3.12、numpy・scipy）:
`python -m unittest discover -s csmap_sheets/tests -t .` → 124件実行、失敗0、スキップ13。

まだ未確認・未実施:
- QGIS 3.44・Windows実機での版表示（説明欄・ログ）の確認。
- v0.10.0から引き続き、Windows実機での傾斜計算方式の選択・出力の確認。

===============================================================================
v0.11.0 — 出力座標系の自動推定の拡張、国土地理院DEMの読み込み修正、個別選択の上限変更（2026-10-03）

内容:
- v0.10.1（タグ・配布なし）の内容を含む。
- 出力の平面直角座標系が空欄のとき、ラスター以外の入力でも系を推定する（`engine/zone_inference.py`）。
  入力の水平座標系、LAS/LAZのヘッダー（VLR/EVLR）、国土地理院DEMの地域メッシュ番号と同梱の対応表
  （`engine/data/jpr_zone_mesh.csv`。総務省統計局「市区町村別メッシュ・コード一覧」から作成）を使う。
- 国土地理院DEM（GML）の走査順`+x-y`のy軸の符号を地理的な向きとして解釈するよう修正（`engine/gsi_dem.py`）。
- 個別ファイル選択の上限を30件にし、`checkParameterValues()`で件数と存在を先に検査する。

検証（ユニットテスト）:
- 走査順：実データと同じ形（`+x-y`、`startPoint 0 0`、全セル分のタプル）、北向き（`+x+y`）、タプル過多の拒否。
- 系の推定：入力の水平座標系、LASヘッダー（GeoKeyDirectory・WKT）、LEMの系番号との照合、
  地域メッシュから系（既知の23地点。甲府・箱根・札幌・函館・釧路・父島・南大東・石垣・那覇・名瀬・鹿児島など）。

検証（QGIS 3.44・Windows実機、2026-10-03、ユーザー実施）:
- 国土地理院DEM（DEM1A、XML、メッシュ5338-44、山梨県）を「入力の水平座標系」「出力の平面直角座標系」とも
  空欄で実行し、第VIII系（EPSG:6676）が推定され、12図郭とXYZタイル（z12～16）が出力された（約4分23秒）。
- 出力を地理院地図（淡色地図）に重ね、地形の向き（南北）が一致することを目視で確認した。
  白く抜けた範囲は、入力に含まれないメッシュ（欠け）であることをユーザーが確認した。

まだ未確認・未実施:
- 個別ファイル選択で31件以上を選んだ場合の実機の挙動（すぐに案内が出て止まるか）。
- 森林航空レーザ成果（LEM）・LAS/LAZでの系の推定の実機確認。
- 系の区分の規則（北海道・東京都・沖縄県・鹿児島県の例外）と告示原文との照合。

===============================================================================
v0.12.0 — σの指定方式（m／px）、処理画面の区分、WebP既定の非可逆化（2026-10-04）

内容:
- 曲率用平滑化のσを、地上距離（sigma_m）に加えて計算格子の画素数（sigma_unit="px"、sigma_px）でも
  指定できるようにした（ユーザー承認の改訂依頼、2026-10-03）。m方式は従来と同じ浮動小数点の式。
- 処理画面の項目を区分ごとに並べ、表示名の先頭に【区分】を付けた。内部名・既定値・範囲は不変。
- WebPタイルの既定を非可逆（QUALITY 75）に変更（ユーザー決定、2026-10-04）。
- 国土地理院DEMのモザイクのログ表記、XYZ候補数の事前見積りテストの修正。

検証（ユニットテスト）:
- σ：依頼書の完了条件（0.5m・1m・2m格子でのm方式3m／px方式3pxの実効σとカーネル幅）、
  旧設定のm方式読込みと半径の式の一致、σ=0、不正値の拒否、分割計算と図郭の継ぎ目
  （GDAL結合テスト：0.5m格子でm方式1mとpx方式2pxの図郭が完全一致）。
- 処理画面：QGISの代わりの仮モジュールで、変更前後のパラメーター92件の名前・既定値・範囲・
  詳細フラグが同じこと。

テスト結果:
- GDAL/QGISなし（CIと同じ構成、Python 3.12）：失敗0。
- GDAL 3.8＋scipy：失敗0（v0.11.0までGDAL環境で失敗していたtest_xyzの事前見積りテストは、
  テストのデータの誤りを直して成功）。

検証（QGIS 3.44・Windows実機、2026-10-03、ユーザー実施）:
- 個別ファイル選択で194件を選ぶと、処理開始前に上限30件の案内が出て停止した（v0.11.0の変更の確認）。
- σの指定方式の欄が処理画面に表示されること（試験用ZIP）。

まだ未確認・未実施:
- 区分付きの処理画面の実機での見え方、σのpx方式での実機の実行結果とログ、設定プロファイルの保存と再読込。
- WebP非可逆（QUALITY 75）のタイルの容量と見た目の実データでの比較。
- メッシュ→系の対応表の元データ（総務省統計局）の利用条件の確認（CLAUDE.mdの「データの利用条件」による）。

===============================================================================
2026-10-04 メッシュ→系の対応表の規則と、告示照合済みの市区町村別データとの突き合わせ

目的:
- v0.11.0で「告示原文との照合は未実施」としていた、対応表の作成規則（tools/build_jpr_zone_table.py。
  都道府県単位の系と、北海道・東京都・沖縄県・鹿児島県の例外）を確かめる。

比べたもの:
- こちら：総務省統計局「市区町村別メッシュ・コード一覧」（令和2年10月1日現在の市区町村区域）の各3次メッシュの
  四隅に規則を当てて得た、市区町村ごとの系番号の集合。
- 参照：姉妹リポジトリ yo-1/japan-plane-rectangular-cs-zones の data/municipality_zones.csv
  （コミット a4688be、2026-10-04）。国土数値情報（行政区域）2026年版に、告示の全文と照合済みの規則
  （同リポジトリの VALIDATION.txt）で系番号を付けたもの。

結果（tools/crosscheck_zone_table.py）:
- 比較できた1,895件の市区町村で、系番号の集合の食い違いは0件。系が分かれる市町村（小笠原村、
  薩摩川内市、南さつま市、三島村、十島村）も、分かれ方の組み合わせが一致した。
- 比較できなかったもの：境界未定地域（13199。こちらは系を確定できないものとして0を記録）、
  浜松市の旧区7件（22131〜22137。2024年の区の再編により参照側は新しい区のコード）。静岡県は全域VIII系。

残る仮定・未確認:
- 告示の「奄美群島」を奄美群島振興開発特別措置法の定義（奄美市及び大島郡）と同じ範囲とみなす扱いは、
  こちらと参照側に共通の仮定のまま（参照側でも公式の定義は見当たらないとしている）。
- 突き合わせは市区町村単位。3次メッシュの四隅で判定するこちらの方式による、系の境目付近のメッシュの扱いは
  別途、既知の地点のテスト（tests/test_zone_inference.py）で確認している。

===============================================================================
2026-10-04 同梱データ（メッシュ→系の対応表）の利用条件の確認

- CLAUDE.mdの「データの利用条件」に従い、engine/data/jpr_zone_mesh.csv の元データ（総務省統計局
  「市区町村別メッシュ・コード一覧」）の提供者の利用条件を確認した。
- 確認結果：政府統計の総合窓口（e-Stat）利用規約（政府標準利用規約に準拠、https://www.e-stat.go.jp/terms-of-use ）
  による。出典の記載、加工した旨の記載（出典とは別に）、政府機関が作成したかのような誤認を与えないことを条件に、
  複製・加工・再配布・商用利用ができる。コード一覧には第三者の権利が含まれるものはないと判断した。
- 確認はユーザーが行った（このリポジトリの開発環境からは stat.go.jp・e-stat.go.jp を閲覧できないため）。
- 対応：SOURCES.md に元データの名称・URL、利用条件のURL、使用した一覧（令和2年10月1日現在、2026-10-03取得）、
  加工の内容と加工した者を記載し、README_ja.md でソースコードのライセンス（GPL-3.0-only）と分けて案内した。
- 未確認：利用規約のページの文面そのものは、この開発環境からは確認できていない。

===============================================================================
2026-10-10 設定プロファイルの保存・読み込み

- 保存済みJSONには、画面に出ないエンジンの上限設定も含まれる。読み込み側が画面項目だけを
  許可していたため、自身で保存した設定を「不明な項目」として拒否する不具合を修正した。
- 設定項目の検証はvalidate_configに統一。未知の項目は拒否し、入力・出力先・出力CRS・入力形式・
  標高単位の確認は、JSONで上書きせず現在の指定を優先する。
- JSONのルートまたはsettingsがオブジェクトでない場合は、明確なProcessingエラーで停止する。
- QGIS 3.44.14で保存→読み込み、BOM付きJSON、画素単位のσ、傾斜方式、WebP品質、
  保護項目、未知の項目、不正なJSON構造を検証した。
- 全177テスト：176成功、1スキップ、失敗0。スキップはWEBPドライバがある環境での
  「WEBPドライバなし」の確認。QGIS APIの4テストも再実行して成功。
- 未確認：GUIのファイル選択・操作、Windows実機、実DEMの位置・色調。

===============================================================================
2026-10-11 Windows実機の設定保存・読み込みと出力比較

- 利用者提供のQGIS 3.44.13（Windows）の実行ログで、設定JSON保存後の処理と、
  外部JSONプロファイルとして読み直した処理の両方が完了したことを確認した。
  Windows上での入力ファイル選択、設定保存・読み込み、画像表示もスクリーンショットで確認した。
- 利用者提供の設定JSONと同じ入力DEMで、QGIS 3.44.14のクラウド環境から再生成した。
  Windows出力の結合GeoTIFFと、サイズ800×1200、4バンド、EPSG:6677、0.5m格子が一致。
  RGBと透明度を含む全画素が完全一致（有効画素270727、RGB最大差0）した。
- 以前のクラウド出力とのRGB差は、同じ設定JSONで比較した結果ではなかった。
  今回は配色を含む設定をそろえて一致を確認した。任意のDEM・環境での一致を保証するものではない。
- 地理院地図との重ね合わせで、河川・横断構造物・道路の概ねの位置対応を確認した。
  この比較は補助的な確認であり、入力DEM自体の測量精度の評価は今回の処理検証の対象外とする。
- 利用者は今回の配色を良好と評価し、当該DEMの基準設定として採用した。
  独立した正解CS画像との色調比較は、基準画像がないため未実施。

===============================================================================
2026-10-11 処理で生じる座標・格子の変化の確認

- 入力DEM自体の測量誤差と、格子整合・図郭切り出し・出力処理で生じる誤差を区別する。
  現地既知点の提供や入力DEMの測量精度評価は、今回の修正の完了条件には含めない。
- 実DEMの入力格子原点は東西-50294.25m、南北-74520.75m。0.5m格子への整合後は
  -50294.5m、-74520.5mとなる。原点の変化（-0.25m、+0.25m）はGDALの
  targetAlignedPixelsによる格子合わせで、標高値は元の地上座標を参照して双一次補間する。
  地物を一律に0.25m移動する処理ではない。
- 入力・整合後DEM・結合GeoTIFFのCRSと0.5m解像度が一致することを確認した。
  整合後DEMと各図郭・結合画像は同じ格子上にあり、追加の半画素ずれは見られなかった。
  2図郭のワールドファイルとGeoTIFFから計算した画素中心座標との差は0mだった。
- Windowsとクラウドの全画素一致は再現性の検証として扱い、処理誤差がゼロである証明とはしない。
  今回の格子・座標メタデータの整合確認も、全処理の無誤差や公共測量の規定への適合を証明するものではない。

2026-10-11 QGIS申請前の事前検査
- Flake8 7.4.1: csmap_sheets/とtools/の指摘6301件を修正し0件。
  ワイルドカードimportを明示化、未使用変数/importを除去、空白・改行を整理。
- Bandit 1.9.4: 本体（tests除外）0件。XMLはサイズ上限とDTD/entity拒否を維持し、
  デコード後の検査を追加。UTF-16のDTD拒否を回帰テストで確認。
  ElementTreeはこの事前拒否、PDAL subprocessはargv/shell=Falseを理由として
  限定したnosecを記録（検査全体の無効化ではない）。
- detect-secrets 1.5.0: 本体・tests・toolsの検出0件。
  既存画像SHA256の定数のみ、秘密情報ではないことを明記して例外登録。
- metadataのhomepage/repository/trackerを公開GitHubに設定。
  作者メールは作者指定により既存MORIZONの公開連絡先と同じものを使用。
- tools/check_plugin_package.pyで必須metadata、URL形式、必須ファイル、
  ZIP整合性を検査。tests/キャッシュを除外し通常ファイル権限で27ファイルを梱包。
- QGIS 3.44.14: 178件中177件成功、1件スキップ、失敗0件。
- 採用済みWindows出力との再比較: RGB/透明度を含め全画素一致、最大差0。
- GitHub Actionsに事前検査を追加。公式QGISサイトの審査は未実施。

2026-10-11 QGIS申請チェックリストへの対応
- metadata.txtのdescription/aboutに英語説明を追加。日本語の詳細説明も維持。
- 必須metadata・ZIP整合性・梱包ファイルを配布スクリプトで再検査。
- QGISサイトへのアップロードおよび審査は未実施。

2026-10-11 QGISサイト検査結果とQt6準備
- 作者提供の画面で0.12.0のSecurity & Quality検査100%、警告0・重大0を確認。
- 同画面のQt6 Checkは赤い×。サイトの検査詳細は未取得。
- QActionのQt6 QtGui移動に対応し、Qt5ではQtWidgetsへフォールバック。
  QEvent.Type.Wheelを使用。QGIS 3.44/Qt5で178件中177成功、1スキップ。
- Qt6環境の実行検証・サイト再検査は未実施。これだけでQt6完全対応とは扱わない。

2026-10-11 0.12.1再申請準備
- 作者提供のQt6検査画面の20件を確認。19件はalgorithm.pyの列挙値、
  1件はplugin.pyのQEvent.Wheel（先行修正済み）。
- Number.Type.Double/Integer、File.Behavior.Folder、
  Processing.SourceType.TypeFile/TypeRasterへ更新。
- QGIS 3.44/Qt5: 178件中177成功、1スキップ、失敗0。
- Flake8、コンパイル、ZIP（27ファイル）とgit diff --check成功。
- サイトのQt6再検査、Qt6環境での実行、0.12.1アップロードは未実施。
