增加坡度计算
This commit is contained in:
103
slope/caijian.py
Normal file
103
slope/caijian.py
Normal file
@ -0,0 +1,103 @@
|
||||
import rasterio
|
||||
from rasterio.warp import reproject, Resampling
|
||||
import numpy as np
|
||||
|
||||
def merge_angles_with_slope_aspect(angle_file, slope_aspect_file, output_file,
|
||||
resampling=Resampling.bilinear):
|
||||
"""
|
||||
将坡度坡向波段添加到角度文件波段之后,输出合并的ENVI DAT文件。
|
||||
输出文件的空间范围、分辨率、投影与角度文件完全一致。
|
||||
掩膜基于角度文件的忽略值生成:只有所有角度波段均为忽略值(0.0)的像素才被设为忽略值。
|
||||
输出文件的忽略值统一设为0.0。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
angle_file : str
|
||||
包含角度信息的ENVI DAT文件(如传感器角度、太阳角度等,至少1个波段)。
|
||||
该文件定义了输出的目标空间参数和掩膜基准。
|
||||
slope_aspect_file : str
|
||||
包含坡度坡向的ENVI DAT文件(通常2个波段)。
|
||||
output_file : str
|
||||
输出合并后的ENVI DAT文件路径(将自动生成.hdr头文件)。
|
||||
resampling : rasterio.warp.Resampling, optional
|
||||
重采样方法,默认为双线性(适用于连续参数)。
|
||||
"""
|
||||
# 1. 从角度文件获取目标空间参数、数据、掩膜
|
||||
with rasterio.open(angle_file) as src_angle:
|
||||
# 目标空间参数
|
||||
dst_transform = src_angle.transform
|
||||
dst_width = src_angle.width
|
||||
dst_height = src_angle.height
|
||||
dst_crs = src_angle.crs
|
||||
|
||||
# 读取角度文件所有波段
|
||||
angle_data = src_angle.read() # shape: (bands, rows, cols)
|
||||
angle_count = src_angle.count
|
||||
|
||||
# 获取角度文件的忽略值(如果未定义,默认为0.0,但应给出警告)
|
||||
angle_nodata = src_angle.nodata
|
||||
if angle_nodata is None:
|
||||
print("警告:角度文件未定义忽略值,将使用0.0作为默认忽略值。")
|
||||
angle_nodata = 0.0
|
||||
|
||||
# 构建掩膜:所有波段均不等于忽略值视为有效
|
||||
mask = np.all(angle_data != angle_nodata, axis=0)
|
||||
|
||||
# 2. 处理坡度坡向文件:重采样到目标空间
|
||||
with rasterio.open(slope_aspect_file) as src_sa:
|
||||
src_sa_data = src_sa.read() # shape: (bands, src_sa_height, src_sa_width)
|
||||
src_sa_count = src_sa.count
|
||||
src_sa_crs = src_sa.crs
|
||||
src_sa_transform = src_sa.transform
|
||||
|
||||
sa_resampled = np.empty((src_sa_count, dst_height, dst_width),
|
||||
dtype=src_sa_data.dtype)
|
||||
|
||||
reproject(
|
||||
source=src_sa_data,
|
||||
destination=sa_resampled,
|
||||
src_transform=src_sa_transform,
|
||||
src_crs=src_sa_crs,
|
||||
dst_transform=dst_transform,
|
||||
dst_crs=dst_crs,
|
||||
resampling=resampling
|
||||
)
|
||||
|
||||
# 3. 合并波段:角度在前,坡度坡向在后
|
||||
merged_data = np.concatenate([angle_data, sa_resampled], axis=0)
|
||||
merged_count = merged_data.shape[0]
|
||||
|
||||
# 4. 准备输出元数据(基于角度文件的profile)
|
||||
with rasterio.open(angle_file) as src_angle:
|
||||
out_profile = src_angle.profile.copy()
|
||||
output_nodata = 0.0 # 强制输出忽略值为0.0(用户要求)
|
||||
out_profile.update({
|
||||
'driver': 'ENVI',
|
||||
'height': dst_height,
|
||||
'width': dst_width,
|
||||
'transform': dst_transform,
|
||||
'crs': dst_crs,
|
||||
'count': merged_count,
|
||||
'dtype': merged_data.dtype,
|
||||
'nodata': output_nodata
|
||||
})
|
||||
|
||||
# 5. 应用掩膜:将无效区域的像素设为忽略值0.0
|
||||
for b in range(merged_count):
|
||||
merged_data[b, ~mask] = output_nodata
|
||||
|
||||
# 6. 写入输出文件
|
||||
with rasterio.open(output_file, 'w', **out_profile) as dst:
|
||||
dst.write(merged_data)
|
||||
|
||||
print(f"处理完成!合并后的文件已保存至:{output_file}")
|
||||
print(f"总波段数:{merged_count}(角度 {angle_count} 波段 + 坡度坡向 {src_sa_count} 波段)")
|
||||
print(f"有效像素区域基于角度文件所有波段判断,忽略值统一设为 0.0。")
|
||||
|
||||
# 示例用法
|
||||
if __name__ == "__main__":
|
||||
angle_file = r"E:\is2\yaopu\output\angel.dat" # 角度DAT文件
|
||||
slope_aspect_file = r"E:\is2\yaopu\DEM\slope_aspect_cosine.dat" # 原始坡度坡向DAT
|
||||
out_file = r"E:\is2\yaopu\DEM\angles_with_slope_aspect.dat" # 合并输出
|
||||
|
||||
merge_angles_with_slope_aspect(angle_file, slope_aspect_file, out_file)
|
||||
197
slope/readme.txt
Normal file
197
slope/readme.txt
Normal file
@ -0,0 +1,197 @@
|
||||
================================================================================
|
||||
地形校正模块 - 已知问题与改进方向
|
||||
================================================================================
|
||||
|
||||
【问题描述】
|
||||
-----------
|
||||
当前版本的 slope_aspectV2.py 在处理推扫式无人机高光谱图像时,存在一个关键假设:
|
||||
整幅图像使用单一的太阳天顶角和方位角来计算 cosine_i(入射角余弦)。
|
||||
|
||||
然而,实际数据采集情况是:
|
||||
- 高光谱图像由3个架次的无人机飞行拼接而成
|
||||
- 每个架次的飞行时间不同(通常间隔数分钟至数十分钟)
|
||||
- 因此,不同扫描线(或不同图像区域)对应的太阳角度是不同的
|
||||
- 但目前代码仅基于DEM中心点坐标计算一组太阳角度,并应用于整幅图像
|
||||
|
||||
【技术影响】
|
||||
-----------
|
||||
这个简化会导致以下问题:
|
||||
|
||||
1. 时间跨度误差
|
||||
- 假设3个架次间隔30分钟,太阳方位角可能变化7-8度(取决于纬度)
|
||||
- 在日出/日落时段,太阳高度角变化可达10-15度
|
||||
- 这会导致不同架次交界处的 cosine_i 出现系统性偏差
|
||||
|
||||
2. 地形校正不准确
|
||||
- 东向坡面:过早架次会低估照明,过晚架次会高估照明
|
||||
- 西向坡面:与东向坡面相反
|
||||
- 交界区域会出现明显的"拼接缝"现象
|
||||
|
||||
3. BRDF校正累积误差
|
||||
- cosine_i 是许多BRDF模型的关键输入参数
|
||||
- 角度误差会传播到后续的BRDF地形校正结果
|
||||
|
||||
【当前代码实现】
|
||||
---------------
|
||||
文件: slope/slope_aspectV2.py
|
||||
|
||||
当前流程:
|
||||
1. 读取DEM -> 2. 计算坡度/坡向 -> 3. 获取DEM中心点坐标(lat, lon)
|
||||
4. 根据单一时间计算一组太阳角度(solar_zn, solar_az)
|
||||
5. 将这组角度应用于整幅图像的所有像素
|
||||
|
||||
关键代码段(约110-130行):
|
||||
```python
|
||||
# 获取DEM中心点坐标
|
||||
latitude, longitude = get_dem_center_coords(dem) # 仅一个中心点!
|
||||
|
||||
# 计算太阳角度(仅一组)
|
||||
solar_zn_deg, solar_az_deg = calc_solar_angles(latitude, longitude, observation_time)
|
||||
|
||||
# 应用于整幅图像
|
||||
cosine_i = calc_cosine_i(solar_zn_rad, solar_az_rad, aspect_rad, slope_rad)
|
||||
```
|
||||
|
||||
【需要的改进方向】
|
||||
-----------------
|
||||
接手人员需要进行以下修改:
|
||||
|
||||
方案A: 分架次处理(推荐)
|
||||
----------------------------
|
||||
如果已知3个架次的地理分界线或时间范围:
|
||||
|
||||
1. 输入格式修改
|
||||
- 接收3组时间参数(或从飞行日志自动提取)
|
||||
- 例如: -t1 "2024-06-15 08:30:00" -t2 "2024-06-15 09:00:00" -t3 "2024-06-15 09:30:00"
|
||||
|
||||
2. 空间分区处理
|
||||
- 根据每架次的覆盖范围(可从高光谱图像的GLT或地理坐标确定)
|
||||
- 将DEM分割为3个区域
|
||||
- 每个区域使用对应架次的时间计算太阳角度
|
||||
|
||||
3. 修改后的伪代码示例:
|
||||
```python
|
||||
# 定义3个架次的时间和地理范围
|
||||
flight_segments = [
|
||||
{'time': '2024-06-15 08:30:00', 'row_range': (0, 1200)}, # 架次1: 行0-1199
|
||||
{'time': '2024-06-15 09:00:00', 'row_range': (1200, 2800)}, # 架次2: 行1200-2799
|
||||
{'time': '2024-06-15 09:30:00', 'row_range': (2800, 4000)}, # 架次3: 行2800-3999
|
||||
]
|
||||
|
||||
# 初始化cosine_i数组
|
||||
cosine_i = np.zeros_like(slope_data)
|
||||
|
||||
# 分段计算
|
||||
for segment in flight_segments:
|
||||
dt = parse_datetime(segment['time'])
|
||||
solar_zn, solar_az = calc_solar_angles(lat, lon, dt)
|
||||
|
||||
y1, y2 = segment['row_range']
|
||||
cosine_i[y1:y2, :] = calc_cosine_i(
|
||||
np.deg2rad(solar_zn), np.deg2rad(solar_az),
|
||||
aspect_rad[y1:y2, :], slope_rad[y1:y2, :]
|
||||
)
|
||||
```
|
||||
|
||||
方案B: 逐行/逐像素太阳角度(更精确)
|
||||
-------------------------------------
|
||||
如果需要更高精度(考虑太阳角度在单架次内的缓慢变化):
|
||||
|
||||
1. 输入要求
|
||||
- 高光谱图像的每一行对应的时间戳(可从IMU/GPS数据提取)
|
||||
- 或者:每个像素的精确UTC时间
|
||||
|
||||
2. 逐行计算
|
||||
```python
|
||||
# 假设times是一个与图像行数相同长度的一维数组
|
||||
# times[i] = 第i行的观测时间(datetime对象)
|
||||
|
||||
for row in range(n_rows):
|
||||
dt = observation_times[row] # 该行的具体时间
|
||||
solar_zn, solar_az = calc_solar_angles(lat, lon, dt)
|
||||
|
||||
cosine_i[row, :] = calc_cosine_i(
|
||||
np.deg2rad(solar_zn), np.deg2rad(solar_az),
|
||||
aspect_rad[row, :], slope_rad[row, :]
|
||||
)
|
||||
```
|
||||
|
||||
3. 性能优化
|
||||
- 由于pvlib计算较慢,可对时间相近的行批量计算
|
||||
- 例如:每10行计算一次太阳角度,中间行插值
|
||||
|
||||
方案C: 从飞行日志自动提取(最自动化)
|
||||
--------------------------------------
|
||||
如果飞行平台记录了GPS/IMU数据:
|
||||
|
||||
1. 输入数据
|
||||
- 读取POS数据(位置、姿态、时间)
|
||||
- 或从高光谱图像的GLT(地理位置查找表)提取
|
||||
|
||||
2. 建立时间-位置映射
|
||||
- 根据无人机位置和时间建立每行图像的观测时间
|
||||
|
||||
3. 分区域计算cosine_i
|
||||
|
||||
【数据结构参考】
|
||||
---------------
|
||||
典型的推扫式高光谱数据结构:
|
||||
|
||||
高光谱图像 (lines, samples, bands)
|
||||
- lines: 扫描线数(飞行方向的像素数)
|
||||
- samples: 每行的像素数(垂直飞行方向)
|
||||
- 第i行对应无人机的第i个位置
|
||||
|
||||
关键信息来源:
|
||||
1. 图像头文件 (.hdr) - 通常包含起始时间和积分时间
|
||||
2. GLT文件 (Geographic Lookup Table) - 每个像素的地理坐标
|
||||
3. 飞行日志/POS数据 - 每行对应的时间戳
|
||||
4. 帧辅助数据 (Frame Auxiliary Data) - 部分传感器自带每帧时间
|
||||
|
||||
【建议的输入接口修改】
|
||||
----------------------
|
||||
当前命令行接口:
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15 10:00:00"
|
||||
|
||||
建议扩展为:
|
||||
# 方式1: 手动指定3个架次的时间和行范围
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat \
|
||||
--segments "0,1200,2024-06-15T08:30:00" \
|
||||
--segments "1200,2800,2024-06-15T09:00:00" \
|
||||
--segments "2800,4000,2024-06-15T09:30:00"
|
||||
|
||||
# 方式2: 从飞行日志文件自动读取
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat --time-log flight_log.csv
|
||||
|
||||
# 方式3: 从高光谱图像头文件提取时间信息
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat --hyperspectral-ref image.hdr
|
||||
|
||||
【验证方法】
|
||||
-----------
|
||||
修改后应验证以下指标:
|
||||
|
||||
1. 连续性检查
|
||||
- 目视检查3个架次交界处的cosine_i是否连续
|
||||
- 统计交界行前后10行的cosine_i差异
|
||||
|
||||
2. 物理合理性
|
||||
- 东向坡的cosine_i应随时间增加而增加(上午飞行)
|
||||
- 西向坡的cosine_i应随时间增加而减小(上午飞行)
|
||||
|
||||
3. 与GLT对比
|
||||
- 如果高光谱有GLT,可根据每像素的太阳角度公式验证
|
||||
|
||||
【相关文件】
|
||||
-----------
|
||||
- slope_aspectV2.py - 当前主程序(需要修改)
|
||||
- angle_compute.py - 角度计算工具(可能包含相关逻辑)
|
||||
- 高光谱图像的.hdr文件 - 可能包含时间元数据
|
||||
- 飞行日志/POS数据 - 外部时间参考
|
||||
|
||||
【优先级建议】
|
||||
--------------
|
||||
1. 【高】首先实现方案A(分架次处理)- 工作量适中,效果显著
|
||||
2. 【中】评估是否需要方案B(逐行)- 取决于太阳角度变化幅度
|
||||
3. 【低】长期可考虑方案C(全自动)- 需要整合飞行数据解析
|
||||
|
||||
简单来说在计算的时候,需要加入角度文件,进行逐像素的cosi计算
|
||||
83
slope/slope_aspect.py
Normal file
83
slope/slope_aspect.py
Normal file
@ -0,0 +1,83 @@
|
||||
import xdem
|
||||
import rasterio
|
||||
import numpy as np
|
||||
|
||||
def calc_cosine_i(solar_zn, solar_az, aspect, slope):
|
||||
"""Generate cosine i image. The cosine of the incidence angle (i) is
|
||||
defined as the angle between the normal to the pixel surface
|
||||
and the solar zenith direction.
|
||||
All input geometry units must be in radians.
|
||||
|
||||
Args:
|
||||
solar_az (numpy.ndarray): Solar azimuth angle.
|
||||
solar_zn (numpy.ndarray): Solar zenith angle.
|
||||
aspect (numpy.ndarray): Ground aspect.
|
||||
slope (numpy.ndarray): Ground slope.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: Cosine i image.
|
||||
"""
|
||||
relative_az = aspect - solar_az
|
||||
cosine_i = (np.cos(solar_zn) * np.cos(slope) +
|
||||
np.sin(solar_zn) * np.sin(slope) * np.cos(relative_az))
|
||||
return cosine_i
|
||||
|
||||
# ========== 1. 读取 DEM ==========
|
||||
dem = xdem.DEM("E:/is2/yaopu/DEM/dsm.tif")
|
||||
|
||||
# ========== 2. 计算坡度、坡向 ==========
|
||||
slope = xdem.terrain.slope(dem, method='ZevenbergThorne')
|
||||
aspect = xdem.terrain.aspect(dem)
|
||||
|
||||
# ========== 3. 获取空间参考信息 ==========
|
||||
transform = dem.transform
|
||||
crs = dem.crs
|
||||
|
||||
# ========== 4. 转换为 numpy 数组 ==========
|
||||
slope_data = slope.data
|
||||
aspect_data = aspect.data
|
||||
|
||||
# ========== 5. 太阳角度(单位:度,根据实际飞行时间和地理位置设置) ==========
|
||||
solar_zn_deg = 30.0 # 太阳天顶角(从天顶到太阳的夹角),例如 30°
|
||||
solar_az_deg = 180.0 # 太阳方位角(从北顺时针),例如 180°(正南)
|
||||
|
||||
# 转换为弧度(因为三角函数需要弧度)
|
||||
solar_zn_rad = np.deg2rad(solar_zn_deg)
|
||||
solar_az_rad = np.deg2rad(solar_az_deg)
|
||||
|
||||
# 坡度和坡向也转换为弧度
|
||||
slope_rad = np.deg2rad(slope_data)
|
||||
aspect_rad = np.deg2rad(aspect_data)
|
||||
|
||||
# ========== 6. 计算 cosine_i ==========
|
||||
cosine_i = calc_cosine_i(solar_zn_rad, solar_az_rad, aspect_rad, slope_rad)
|
||||
|
||||
# ========== 7. 处理无效值(NaN) ==========
|
||||
# 注意:将 NaN 替换为 0 可能会与真实值为 0 的像元混淆(如水平面坡度为0,阴影边缘cosine_i=0)。
|
||||
# 如需严格区分,建议使用特殊值(如 -9999)作为 NoData。
|
||||
slope_data = np.nan_to_num(slope_data, nan=0.0)
|
||||
aspect_data = np.nan_to_num(aspect_data, nan=0.0)
|
||||
cosine_i = np.nan_to_num(cosine_i, nan=0.0)
|
||||
|
||||
# ========== 8. 堆叠为多波段数组(坡度、坡向、cosine_i) ==========
|
||||
stacked = np.stack([slope_data, aspect_data, cosine_i], axis=0)
|
||||
|
||||
# ========== 9. 输出 ENVI 格式文件 ==========
|
||||
output_path = "E:/is2/yaopu/DEM/slope_aspect_cosine.dat"
|
||||
|
||||
with rasterio.open(
|
||||
output_path,
|
||||
'w',
|
||||
driver='ENVI',
|
||||
height=stacked.shape[1],
|
||||
width=stacked.shape[2],
|
||||
count=stacked.shape[0],
|
||||
dtype=stacked.dtype,
|
||||
crs=crs,
|
||||
transform=transform,
|
||||
nodata=0.0, # 与上面替换的 NaN 值一致
|
||||
) as dst:
|
||||
dst.write(stacked)
|
||||
|
||||
print(f"已保存多波段 ENVI 文件:{output_path}")
|
||||
print(f"波段顺序:1-坡度(°), 2-坡向(°), 3-cosine_i")
|
||||
381
slope/slope_aspectV2.py
Normal file
381
slope/slope_aspectV2.py
Normal file
@ -0,0 +1,381 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
DEM地形处理工具 - 计算坡度、坡向和太阳入射角余弦
|
||||
|
||||
该工具从DEM文件中提取坐标信息,根据给定时间计算太阳位置,
|
||||
并生成坡度、坡向和cosine_i的多波段ENVI文件。
|
||||
|
||||
作者: BRDF_GUI Team
|
||||
版本: 2.0.0
|
||||
"""
|
||||
|
||||
import xdem
|
||||
import rasterio
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import argparse
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import pvlib.solarposition as solarposition
|
||||
from rasterio.crs import CRS
|
||||
import pyproj
|
||||
|
||||
|
||||
def calc_cosine_i(solar_zn, solar_az, aspect, slope):
|
||||
"""
|
||||
计算入射角余弦(cosine i)。
|
||||
|
||||
入射角余弦定义为地表法线与太阳天顶方向之间夹角的余弦值。
|
||||
所有角度输入必须为弧度。
|
||||
|
||||
Args:
|
||||
solar_az (numpy.ndarray): 太阳方位角(弧度)
|
||||
solar_zn (numpy.ndarray): 太阳天顶角(弧度)
|
||||
aspect (numpy.ndarray): 地面坡向(弧度)
|
||||
slope (numpy.ndarray): 地面坡度(弧度)
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: 入射角余弦图像
|
||||
"""
|
||||
relative_az = aspect - solar_az
|
||||
cosine_i = (np.cos(solar_zn) * np.cos(slope) +
|
||||
np.sin(solar_zn) * np.sin(slope) * np.cos(relative_az))
|
||||
return cosine_i
|
||||
|
||||
|
||||
def get_dem_center_coords(dem):
|
||||
"""
|
||||
获取DEM图像中心点的经纬度坐标。
|
||||
|
||||
Args:
|
||||
dem: xdem.DEM对象
|
||||
|
||||
Returns:
|
||||
tuple: (纬度, 经度) - 单位:度
|
||||
"""
|
||||
# 获取DEM的范围边界
|
||||
bounds = dem.bounds # (left, bottom, right, top)
|
||||
center_x = (bounds.left + bounds.right) / 2
|
||||
center_y = (bounds.bottom + bounds.top) / 2
|
||||
|
||||
# 如果DEM是投影坐标系,需要转换为WGS84经纬度
|
||||
if dem.crs is not None and not dem.crs.is_geographic:
|
||||
# 创建从投影坐标系到WGS84的转换
|
||||
transformer = pyproj.Transformer.from_crs(dem.crs, CRS.from_epsg(4326), always_xy=True)
|
||||
lon, lat = transformer.transform(center_x, center_y)
|
||||
else:
|
||||
# 已经是地理坐标系(经纬度)
|
||||
lon, lat = center_x, center_y
|
||||
|
||||
return lat, lon
|
||||
|
||||
|
||||
def parse_datetime(datetime_str):
|
||||
"""
|
||||
解析日期时间字符串。
|
||||
|
||||
支持的格式:
|
||||
- YYYY-MM-DD HH:MM:SS
|
||||
- YYYY-MM-DDTHH:MM:SS
|
||||
- YYYYMMDD_HHMMSS
|
||||
|
||||
Args:
|
||||
datetime_str (str): 日期时间字符串
|
||||
|
||||
Returns:
|
||||
datetime: datetime对象(UTC时间)
|
||||
"""
|
||||
formats = [
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
"%Y-%m-%dT%H:%M:%S",
|
||||
"%Y%m%d_%H%M%S",
|
||||
"%Y-%m-%d",
|
||||
]
|
||||
|
||||
for fmt in formats:
|
||||
try:
|
||||
dt = datetime.strptime(datetime_str, fmt)
|
||||
# 如果只输入了日期,默认时间为正午12:00
|
||||
if fmt == "%Y-%m-%d":
|
||||
dt = dt.replace(hour=12, minute=0, second=0)
|
||||
return dt
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
raise ValueError(f"无法解析日期时间: {datetime_str}. 支持的格式: YYYY-MM-DD HH:MM:SS, YYYY-MM-DDTHH:MM:SS, YYYYMMDD_HHMMSS")
|
||||
|
||||
|
||||
def get_user_input_datetime():
|
||||
"""
|
||||
交互式获取用户输入的日期和时间。
|
||||
|
||||
Returns:
|
||||
datetime: datetime对象(UTC时间)
|
||||
"""
|
||||
print("请输入观测日期和时间(UTC时间):")
|
||||
|
||||
try:
|
||||
year = int(input(" 年份 (YYYY): "))
|
||||
month = int(input(" 月份 (MM): "))
|
||||
day = int(input(" 日期 (DD): "))
|
||||
hour = int(input(" 小时 (HH, 24小时制): "))
|
||||
minute = int(input(" 分钟 (MM): "))
|
||||
second = int(input(" 秒 (SS): "))
|
||||
|
||||
dt = datetime(year, month, day, hour, minute, second)
|
||||
print(f"\n使用的时间: {dt.strftime('%Y-%m-%d %H:%M:%S')} UTC")
|
||||
return dt
|
||||
except ValueError as e:
|
||||
print(f"输入错误: {e}")
|
||||
print("将使用默认时间 2024-06-15 10:00:00 UTC")
|
||||
return datetime(2024, 6, 15, 10, 0, 0)
|
||||
|
||||
|
||||
def calc_solar_angles(lat, lon, dt):
|
||||
"""
|
||||
使用pvlib计算太阳天顶角和方位角。
|
||||
|
||||
Args:
|
||||
lat (float): 纬度(度)
|
||||
lon (float): 经度(度)
|
||||
dt (datetime): datetime对象(UTC时间)
|
||||
|
||||
Returns:
|
||||
tuple: (太阳天顶角, 太阳方位角) - 单位:度
|
||||
天顶角: 0=天顶, 90=地平线
|
||||
方位角: 从北顺时针,0=北,90=东,180=南,270=西
|
||||
"""
|
||||
# 创建时间索引
|
||||
times = pd.DatetimeIndex([dt])
|
||||
|
||||
# 使用pvlib计算太阳位置
|
||||
solpos = solarposition.get_solarposition(times, lat, lon)
|
||||
|
||||
solar_zn_deg = solpos['zenith'].values[0] # 天顶角
|
||||
solar_az_deg = solpos['azimuth'].values[0] # 方位角
|
||||
|
||||
return solar_zn_deg, solar_az_deg
|
||||
|
||||
|
||||
def write_envi_output(output_path, data, transform, crs, metadata=None):
|
||||
"""
|
||||
将数据写入ENVI格式文件。
|
||||
|
||||
Args:
|
||||
output_path (str): 输出文件路径
|
||||
data (numpy.ndarray): 数据数组 (bands, rows, cols)
|
||||
transform: 地理变换参数
|
||||
crs: 坐标参考系
|
||||
metadata (dict, optional): 额外的元数据
|
||||
"""
|
||||
with rasterio.open(
|
||||
output_path,
|
||||
'w',
|
||||
driver='ENVI',
|
||||
height=data.shape[1],
|
||||
width=data.shape[2],
|
||||
count=data.shape[0],
|
||||
dtype=data.dtype,
|
||||
crs=crs,
|
||||
transform=transform,
|
||||
nodata=0.0,
|
||||
) as dst:
|
||||
dst.write(data)
|
||||
|
||||
# 写入ENVI头文件元数据
|
||||
if metadata:
|
||||
dst.update_tags(**metadata)
|
||||
|
||||
|
||||
def process_dem(dem_path, output_path, datetime_input=None, interactive=False):
|
||||
"""
|
||||
主处理函数:读取DEM,计算坡度坡向,计算太阳角度,输出结果。
|
||||
|
||||
Args:
|
||||
dem_path (str): DEM输入文件路径
|
||||
output_path (str): 输出文件路径
|
||||
datetime_input (str, optional): 日期时间字符串,如果为None则使用交互模式
|
||||
interactive (bool): 是否使用交互式时间输入
|
||||
|
||||
Returns:
|
||||
dict: 处理结果信息
|
||||
"""
|
||||
# ========== 1. 读取 DEM ==========
|
||||
print(f"读取DEM文件: {dem_path}")
|
||||
dem = xdem.DEM(dem_path)
|
||||
|
||||
# ========== 2. 计算坡度、坡向 ==========
|
||||
print("计算坡度和坡向...")
|
||||
slope = xdem.terrain.slope(dem, method='ZevenbergThorne')
|
||||
aspect = xdem.terrain.aspect(dem)
|
||||
|
||||
# ========== 3. 获取空间参考信息 ==========
|
||||
transform = dem.transform
|
||||
crs = dem.crs
|
||||
|
||||
# ========== 4. 转换为 numpy 数组 ==========
|
||||
slope_data = slope.data
|
||||
aspect_data = aspect.data
|
||||
|
||||
# ========== 5. 获取DEM中心点坐标并计算太阳角度 ==========
|
||||
print("\n获取DEM坐标信息...")
|
||||
latitude, longitude = get_dem_center_coords(dem)
|
||||
print(f" 中心点纬度: {latitude:.6f}°")
|
||||
print(f" 中心点经度: {longitude:.6f}°")
|
||||
print(f" 坐标系: WGS84")
|
||||
|
||||
# 获取观测时间
|
||||
if datetime_input:
|
||||
observation_time = parse_datetime(datetime_input)
|
||||
print(f"\n使用命令行提供的时间: {observation_time.strftime('%Y-%m-%d %H:%M:%S')} UTC")
|
||||
elif interactive or not sys.stdin.isatty():
|
||||
observation_time = get_user_input_datetime()
|
||||
else:
|
||||
# 默认时间(如果非交互式且未提供时间)
|
||||
observation_time = datetime(2024, 6, 15, 10, 0, 0)
|
||||
print(f"\n使用默认时间: {observation_time.strftime('%Y-%m-%d %H:%M:%S')} UTC")
|
||||
|
||||
# 计算太阳角度
|
||||
print("\n计算太阳位置...")
|
||||
solar_zn_deg, solar_az_deg = calc_solar_angles(latitude, longitude, observation_time)
|
||||
|
||||
print(f" 太阳天顶角: {solar_zn_deg:.2f}°")
|
||||
print(f" 太阳高度角: {90 - solar_zn_deg:.2f}°")
|
||||
print(f" 太阳方位角: {solar_az_deg:.2f}°")
|
||||
|
||||
# 转换为弧度
|
||||
solar_zn_rad = np.deg2rad(solar_zn_deg)
|
||||
solar_az_rad = np.deg2rad(solar_az_deg)
|
||||
|
||||
# 坡度和坡向也转换为弧度
|
||||
slope_rad = np.deg2rad(slope_data)
|
||||
aspect_rad = np.deg2rad(aspect_data)
|
||||
|
||||
# ========== 6. 计算 cosine_i ==========
|
||||
print("\n计算入射角余弦 (cosine_i)...")
|
||||
cosine_i = calc_cosine_i(solar_zn_rad, solar_az_rad, aspect_rad, slope_rad)
|
||||
|
||||
# ========== 7. 处理无效值(NaN) ==========
|
||||
slope_data = np.nan_to_num(slope_data, nan=0.0)
|
||||
aspect_data = np.nan_to_num(aspect_data, nan=0.0)
|
||||
cosine_i = np.nan_to_num(cosine_i, nan=0.0)
|
||||
|
||||
# ========== 8. 堆叠为多波段数组 ==========
|
||||
stacked = np.stack([slope_data, aspect_data, cosine_i], axis=0)
|
||||
|
||||
# ========== 9. 准备元数据 ==========
|
||||
metadata = {
|
||||
'description': 'Slope, Aspect, and Cosine_i derived from DEM',
|
||||
'band_names': 'slope(deg),aspect(deg),cosine_i',
|
||||
'solar_zenith_angle': str(solar_zn_deg),
|
||||
'solar_azimuth_angle': str(solar_az_deg),
|
||||
'solar_elevation_angle': str(90 - solar_zn_deg),
|
||||
'observation_time_utc': observation_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'dem_center_lat': str(latitude),
|
||||
'dem_center_lon': str(longitude),
|
||||
}
|
||||
|
||||
# ========== 10. 输出 ENVI 格式文件 ==========
|
||||
print(f"\n保存结果到: {output_path}")
|
||||
write_envi_output(output_path, stacked, transform, crs, metadata)
|
||||
|
||||
print("\n处理完成!")
|
||||
print(f" 输出文件: {output_path}")
|
||||
print(f" 波段顺序: 1-坡度(°), 2-坡向(°), 3-cosine_i")
|
||||
|
||||
return {
|
||||
'output_path': output_path,
|
||||
'solar_zenith': solar_zn_deg,
|
||||
'solar_azimuth': solar_az_deg,
|
||||
'center_lat': latitude,
|
||||
'center_lon': longitude,
|
||||
'observation_time': observation_time,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数 - 命令行入口"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='DEM地形处理工具 - 计算坡度、坡向和太阳入射角余弦',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''
|
||||
使用示例:
|
||||
# 基本用法(交互式输入时间)
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat
|
||||
|
||||
# 通过命令行指定时间
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15 10:30:00"
|
||||
|
||||
# 仅指定日期(默认使用正午12:00)
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15"
|
||||
|
||||
# 显示详细处理信息
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15 10:30:00" -v
|
||||
|
||||
支持的日期时间格式:
|
||||
- "YYYY-MM-DD HH:MM:SS" (推荐)
|
||||
- "YYYY-MM-DDTHH:MM:SS" (ISO格式)
|
||||
- "YYYYMMDD_HHMMSS"
|
||||
- "YYYY-MM-DD" (默认使用12:00:00)
|
||||
'''
|
||||
)
|
||||
|
||||
parser.add_argument('-i', '--input', required=True,
|
||||
help='输入DEM文件路径 (支持GeoTIFF等格式)')
|
||||
|
||||
parser.add_argument('-o', '--output', required=True,
|
||||
help='输出ENVI文件路径 (.dat)')
|
||||
|
||||
parser.add_argument('-t', '--time',
|
||||
help='观测日期时间 (UTC),格式: "YYYY-MM-DD HH:MM:SS"。'
|
||||
'如果不提供,将进入交互式输入模式')
|
||||
|
||||
parser.add_argument('--interactive', action='store_true',
|
||||
help='强制使用交互式时间输入模式')
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help='显示详细处理信息')
|
||||
|
||||
parser.add_argument('--version', action='version', version='%(prog)s 2.0.0')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 验证输入文件存在
|
||||
if not Path(args.input).exists():
|
||||
print(f"错误: 输入文件不存在: {args.input}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 创建输出目录(如果不存在)
|
||||
output_dir = Path(args.output).parent
|
||||
if not output_dir.exists():
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
print(f"创建输出目录: {output_dir}")
|
||||
|
||||
try:
|
||||
# 执行处理
|
||||
result = process_dem(
|
||||
dem_path=args.input,
|
||||
output_path=args.output,
|
||||
datetime_input=args.time,
|
||||
interactive=args.interactive
|
||||
)
|
||||
|
||||
if args.verbose:
|
||||
print("\n详细结果:")
|
||||
for key, value in result.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n处理失败: {e}", file=sys.stderr)
|
||||
if args.verbose:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user