#!/usr/bin/env python3 from __future__ import annotations import argparse import json from pathlib import Path from typing import Any, Dict, List def load_manifest(path: Path) -> Dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) def resolve_dem_source(arg_value: str | None, manifest: Dict[str, Any]) -> Path: candidates: List[str] = [] if arg_value: candidates.append(arg_value) resolved = manifest.get("resolved_dependencies", {}) for key in ("dem_path_wsl", "dem_path_windows"): value = resolved.get(key) if value: candidates.append(value) for candidate in candidates: path = Path(candidate) if path.exists() and Path(str(path) + ".xml").exists(): return path raise FileNotFoundError("Unable to resolve source DEM from arguments or stack manifest") def compute_bbox(manifest: Dict[str, Any], margin_deg: float) -> List[float]: lons: List[float] = [] lats: List[float] = [] for scene in manifest["scenes"]: lon = scene.get("scene_center_lon") lat = scene.get("scene_center_lat") if lon is None or lat is None: source_scene_json = scene.get("source_scene_json_wsl") or scene.get("source_scene_json_windows") if source_scene_json and Path(source_scene_json).exists(): source_payload = json.loads(Path(source_scene_json).read_text(encoding="utf-8")) lon = source_payload.get("scene_center_lon") lat = source_payload.get("scene_center_lat") if lon is not None and lat is not None: lons.append(float(lon)) lats.append(float(lat)) if not lons or not lats: raise ValueError("Stack manifest does not include usable scene center coordinates") west = min(lons) - margin_deg east = max(lons) + margin_deg south = min(lats) - margin_deg north = max(lats) + margin_deg return [south, north, west, east] def prepare_dem(source_dem: Path, output_dem: Path, bbox: List[float]) -> None: from osgeo import gdal from isce.applications.gdal2isce_xml import gdal2isce_xml south, north, west, east = bbox src_open_path = Path(str(source_dem) + ".vrt") if not src_open_path.exists(): src_open_path = source_dem output_dem.parent.mkdir(parents=True, exist_ok=True) output_vrt = Path(str(output_dem) + ".vrt") output_xml = Path(str(output_dem) + ".xml") output_hdr = Path(str(output_dem) + ".hdr") fallback_hdr = output_dem.with_suffix(".hdr") src_ds = gdal.Open(str(src_open_path), gdal.GA_ReadOnly) if src_ds is None: raise RuntimeError(f"Unable to open DEM source: {src_open_path}") translate_options = gdal.TranslateOptions( format="ENVI", projWin=[west, north, east, south], ) out_ds = gdal.Translate(str(output_dem), src_ds, options=translate_options) if out_ds is None: raise RuntimeError("gdal.Translate failed while clipping the DEM") out_ds = None src_ds = None vrt_ds = gdal.Open(str(output_dem), gdal.GA_ReadOnly) if vrt_ds is None: raise RuntimeError(f"Unable to reopen clipped DEM: {output_dem}") gdal.Translate(str(output_vrt), vrt_ds, options=gdal.TranslateOptions(format="VRT")) vrt_ds = None gdal2isce_xml(str(output_vrt)) if not output_xml.exists(): raise RuntimeError(f"Expected ISCE XML was not created: {output_xml}") if not output_hdr.exists() and not fallback_hdr.exists(): raise RuntimeError(f"Expected ENVI header was not created: {output_hdr} or {fallback_hdr}") def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Clip a local DEM window for one LT-1 stack workspace." ) parser.add_argument( "--stack-manifest", required=True, help="Path to stack_input_manifest.json generated by build_lt1_stack_prep.py", ) parser.add_argument( "--source-dem", default=None, help="Override source DEM base path.", ) parser.add_argument( "--margin-deg", type=float, default=1.0, help="Margin around stack scene-center extents in degrees.", ) return parser.parse_args() def main() -> int: args = parse_args() manifest_path = Path(args.stack_manifest) if not manifest_path.exists(): raise FileNotFoundError(f"Stack manifest not found: {manifest_path}") manifest = load_manifest(manifest_path) source_dem = resolve_dem_source(args.source_dem, manifest) bbox = compute_bbox(manifest, margin_deg=args.margin_deg) workspace = manifest["workspace"] dem_dir = Path(workspace["inputs_dir_wsl"]) / "dem" output_dem = dem_dir / "stack_dem_window.wgs84" prepare_dem(source_dem=source_dem, output_dem=output_dem, bbox=bbox) report = { "stack_manifest": str(manifest_path), "source_dem": str(source_dem), "output_dem": str(output_dem), "bbox_south_north_west_east": bbox, } report_path = dem_dir / "stack_dem_window_report.json" report_path.write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8") print(f"Source DEM: {source_dem}") print(f"Output DEM: {output_dem}") print(f"BBox: {bbox}") print(f"Report: {report_path}") return 0 if __name__ == "__main__": raise SystemExit(main())