fix: 动态查找 PROJ/GDAL 路径 + Warp 1 像素浮点舍入容错

**water_quality_gui_v2.py:**
- 废弃硬编码 conda_env 路径
- 新增 _find_and_set_gdal_env(): 基于 sys.prefix 自动推断
  Library/share/proj 和 Library/share/gdal,验证 proj.db 存在后
  强设 PROJ_LIB 和 GDAL_DATA 环境变量
- 兼容 Windows (Library/share/...) 和 Linux/Mac (share/...) 路径

**preview_generator.py _warp_mask_to_image():**
- 尺寸验证从严格相等改为 ±2 像素容差
- 新增 _snap_array_to_shape(): warp 输出与 target_shape
  相差 ≤2px 时自动 slice(截断)或 pad(填充),不再回退到盲读模式
- 解决坐标系转换浮点舍入导致的 1048x522 vs 1047x521 问题
This commit is contained in:
duxin
2026-07-07 10:01:31 +08:00
parent 78818bbc50
commit 1f6c81158b
2 changed files with 117 additions and 11 deletions

View File

@ -266,23 +266,87 @@ def _warp_mask_to_image(mask_path: str,
mask_ds = None # 源掩膜已不再需要
# ---- 验证输出尺寸 ----
# ---- 验证输出尺寸(容忍 ±WARP_SIZE_TOLERANCE 像素的浮点舍入) ----
# 坐标系转换时的浮点舍入可能导致 warp 输出与底图相差 1-2 像素,
# 此时地理位置已经对齐,不应回退到直接读取模式。
_WARP_SIZE_TOLERANCE = 2
warped_w = warped_ds.RasterXSize
warped_h = warped_ds.RasterYSize
if warped_w != base_w or warped_h != base_h:
dw = warped_w - base_w
dh = warped_h - base_h
if abs(dw) > _WARP_SIZE_TOLERANCE or abs(dh) > _WARP_SIZE_TOLERANCE:
warped_ds = None
raise RuntimeError(
f"gdal.Warp 输出尺寸 ({warped_w}x{warped_h}) 与底图 "
f"({base_w}x{base_h}) 不一致!请检查投影/分辨率参数。"
f"({base_w}x{base_h}) 差异过大 (dw={dw}, dh={dh})"
f"请检查投影/分辨率参数。"
)
# ---- 读取 warped 数据 ----
warped_data = warped_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
warped_ds = None
# ---- 微调尺寸到 target_shapeslice 多余边缘 或 pad 缺失边缘) ----
if dw != 0 or dh != 0:
warped_data = _snap_array_to_shape(
warped_data, base_h, base_w, fill_value=nodata_output
)
return warped_data, nodata_output
def _snap_array_to_shape(arr: np.ndarray,
target_h: int,
target_w: int,
fill_value: float = _WARP_NODATA) -> np.ndarray:
"""将数组尺寸微调到目标 shape±2 像素容差内)
处理坐标系转换时浮点舍入导致的 1-2 像素差异。
- 数组比目标大 → 从边缘截断多余行/列
- 数组比目标小 → 用 fill_value 填充缺失的行/列
- 因 warp 已确保地理对齐,多出的边缘像素为投影边界舍入产物
Args:
arr: 输入数组shape = (h, w)
target_h: 目标高度(行数)
target_w: 目标宽度(列数)
fill_value: 填充值(默认 _WARP_NODATA = -9999.0
Returns:
调整后的数组shape 精确为 (target_h, target_w)
"""
h, w = arr.shape
dh = h - target_h
dw = w - target_w
if dh == 0 and dw == 0:
return arr
# --- 裁剪多余行/列(优先从末尾截断) ---
if dh > 0:
arr = arr[:target_h, :]
if dw > 0:
arr = arr[:, :target_w]
# --- 填充缺失行/列 ---
if dh < 0:
pad_bottom = abs(dh)
arr = np.pad(arr, ((0, pad_bottom), (0, 0)), mode='constant',
constant_values=fill_value)
if dw < 0:
pad_right = abs(dw)
arr = np.pad(arr, ((0, 0), (0, pad_right)), mode='constant',
constant_values=fill_value)
print(f"[尺寸微调] warp 输出 ({h}x{w}) → 目标 ({target_h}x{target_w})"
f"{'裁剪' if dh > 0 or dw > 0 else ''}"
f"{'填充' if dh < 0 or dw < 0 else ''}"
f"{'/' if dh > 0 or dw > 0 and (dh < 0 or dw < 0) else ''}")
return arr
# ============================================================
# 核心预览图生成函数
# ============================================================

View File

@ -17,15 +17,57 @@ WaterQualityGUI 只负责窗口框架标题栏、菜单栏、状态栏、QTab
"""
import os
# 强行切断系统环境变量的干扰,绑定到当前的 Conda 虚拟环境
# 必须在 import gdal / import rasterio 等任何第三方库之前执行
conda_env = r"D:\111\changyongruanjian\anconda\envs\WQ_GUI"
os.environ["PROJ_LIB"] = os.path.join(conda_env, "Library", "share", "proj")
os.environ["GDAL_DATA"] = os.path.join(conda_env, "Library", "share", "gdal")
import sys
# ═══════════════════════════════════════════════════════════════
# 动态推断当前 Conda 环境下的 GDAL/PROJ 数据路径
# 必须在 import gdal / import rasterio 等任何第三方库之前执行
# ═══════════════════════════════════════════════════════════════
def _find_and_set_gdal_env():
"""根据 sys.prefix 自动推断并强设 PROJ_LIB 和 GDAL_DATA 环境变量"""
# sys.prefix 在 Conda 环境下通常为:
# D:\...\anconda\envs\WQ_GUI
# PROJ 和 GDAL 的数据文件位于:
# {sys.prefix}\Library\share\proj (含 proj.db)
# {sys.prefix}\Library\share\gdal
prefix = sys.prefix
candidates_proj = [
os.path.join(prefix, "Library", "share", "proj"), # Windows Conda
os.path.join(prefix, "share", "proj"), # Linux/Mac Conda
]
candidates_gdal = [
os.path.join(prefix, "Library", "share", "gdal"),
os.path.join(prefix, "share", "gdal"),
]
proj_lib = None
for path in candidates_proj:
proj_db = os.path.join(path, "proj.db")
if os.path.isfile(proj_db):
proj_lib = path
break
gdal_data = None
for path in candidates_gdal:
if os.path.isdir(path):
gdal_data = path
break
if proj_lib:
os.environ["PROJ_LIB"] = proj_lib
print(f"[ENV] PROJ_LIB → {proj_lib}")
else:
print("[ENV] ⚠ 未找到 proj.dbPROJ_LIB 保持系统默认,可能影响坐标转换")
if gdal_data:
os.environ["GDAL_DATA"] = gdal_data
print(f"[ENV] GDAL_DATA → {gdal_data}")
else:
print("[ENV] ⚠ 未找到 GDAL data 目录GDAL_DATA 保持系统默认")
_find_and_set_gdal_env()
import ctypes
import traceback
import multiprocessing