Files
GeoData/scripts/ortho_build_master.py

159 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Build one global orthophoto master from tiled orthophoto JPGs."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from osgeo import gdal
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Create a global ortho master mosaic from ortho tile JPGs.")
p.add_argument(
"--input-dir",
action="append",
default=[],
help="Input directory containing ortho JPG tiles. Can be specified multiple times.",
)
p.add_argument("--pattern", default="*.jpg", help="Input filename pattern.")
p.add_argument("--out-dir", default="work/mask_master", help="Output directory.")
p.add_argument("--vrt-name", default="ortho_master.vrt", help="Output VRT name.")
p.add_argument("--tif-name", default="ortho_master.tif", help="Output GeoTIFF name.")
p.add_argument("--preview-name", default="ortho_master.jpg", help="Output preview JPG name.")
p.add_argument("--no-preview", action="store_true", help="Skip preview JPG generation.")
p.add_argument(
"--preview-max-size",
type=int,
default=8192,
help="Longest preview edge in pixels (aspect preserved).",
)
p.add_argument(
"--compress",
default="LZW",
help="GeoTIFF compression (e.g., LZW, DEFLATE, JPEG, NONE).",
)
p.add_argument("--resample", default="nearest", help="Resample algorithm for preview (nearest|bilinear|cubic...).")
return p.parse_args()
def collect_inputs(input_dirs: list[str], pattern: str) -> list[Path]:
dirs = [Path(d) for d in input_dirs] if input_dirs else [Path("export_unity/ortho_jpg")]
files: list[Path] = []
for d in dirs:
if not d.exists():
print(f"[ortho_build_master] Warning: input dir missing: {d}")
continue
for f in sorted(d.glob(pattern)):
if f.suffix.lower() != ".jpg":
continue
files.append(f)
return files
def preview_size(width: int, height: int, max_edge: int) -> tuple[int, int]:
if width <= 0 or height <= 0:
return width, height
edge = max(width, height)
if edge <= max_edge:
return width, height
scale = max_edge / float(edge)
return max(1, int(round(width * scale))), max(1, int(round(height * scale)))
def main() -> int:
args = parse_args()
gdal.UseExceptions()
inputs = collect_inputs(args.input_dir, args.pattern)
if not inputs:
raise SystemExit("[ortho_build_master] No input ortho tiles found.")
out_dir = Path(args.out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
vrt_path = out_dir / args.vrt_name
tif_path = out_dir / args.tif_name
preview_path = out_dir / args.preview_name
meta_path = out_dir / "ortho_master_meta.json"
print(f"[ortho_build_master] Input tiles: {len(inputs)}")
print(f"[ortho_build_master] Building VRT: {vrt_path}")
vrt = gdal.BuildVRT(str(vrt_path), [str(p) for p in inputs])
if vrt is None:
raise SystemExit("[ortho_build_master] gdal.BuildVRT failed.")
width = vrt.RasterXSize
height = vrt.RasterYSize
gt = vrt.GetGeoTransform(can_return_null=True)
proj = vrt.GetProjectionRef()
print(f"[ortho_build_master] Translating GeoTIFF: {tif_path}")
tif_ds = gdal.Translate(
str(tif_path),
vrt,
options=gdal.TranslateOptions(
format="GTiff",
creationOptions=[
"TILED=YES",
f"COMPRESS={args.compress}",
"BIGTIFF=IF_SAFER",
],
),
)
if tif_ds is None:
raise SystemExit("[ortho_build_master] gdal.Translate to GeoTIFF failed.")
tif_ds = None
if not args.no_preview:
out_w, out_h = preview_size(width, height, args.preview_max_size)
print(f"[ortho_build_master] Writing preview JPG: {preview_path} ({out_w}x{out_h})")
jpg_ds = gdal.Translate(
str(preview_path),
vrt,
options=gdal.TranslateOptions(
format="JPEG",
width=out_w,
height=out_h,
resampleAlg=args.resample,
creationOptions=["QUALITY=92"],
),
)
if jpg_ds is None:
raise SystemExit("[ortho_build_master] gdal.Translate to JPEG preview failed.")
jpg_ds = None
vrt = None
meta = {
"schema_version": 1,
"inputs": [str(p) for p in inputs],
"outputs": {
"vrt": str(vrt_path),
"tif": str(tif_path),
"preview": None if args.no_preview else str(preview_path),
},
"raster": {
"width": width,
"height": height,
"geotransform": list(gt) if gt else None,
"projection": proj,
},
"settings": {
"compress": args.compress,
"preview_max_size": args.preview_max_size,
"resample": args.resample,
"pattern": args.pattern,
"input_dirs": args.input_dir if args.input_dir else ["export_unity/ortho_jpg"],
},
}
meta_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
print(f"[ortho_build_master] Wrote metadata: {meta_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())