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 # 源掩膜已不再需要
|
mask_ds = None # 源掩膜已不再需要
|
||||||
|
|
||||||
# ---- 验证输出尺寸 ----
|
# ---- 验证输出尺寸(容忍 ±WARP_SIZE_TOLERANCE 像素的浮点舍入) ----
|
||||||
|
# 坐标系转换时的浮点舍入可能导致 warp 输出与底图相差 1-2 像素,
|
||||||
|
# 此时地理位置已经对齐,不应回退到直接读取模式。
|
||||||
|
_WARP_SIZE_TOLERANCE = 2
|
||||||
warped_w = warped_ds.RasterXSize
|
warped_w = warped_ds.RasterXSize
|
||||||
warped_h = warped_ds.RasterYSize
|
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
|
warped_ds = None
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"gdal.Warp 输出尺寸 ({warped_w}x{warped_h}) 与底图 "
|
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 数据 ----
|
||||||
warped_data = warped_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
warped_data = warped_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
||||||
warped_ds = None
|
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
|
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
|
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
|
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 ctypes
|
||||||
import traceback
|
import traceback
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
|||||||
Reference in New Issue
Block a user