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:
@ -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_shape(slice 多余边缘 或 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
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 核心预览图生成函数
|
||||
# ============================================================
|
||||
|
||||
@ -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.db,PROJ_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
|
||||
|
||||
Reference in New Issue
Block a user