2022年 11月 9日

Python计算DEM(tif格式)坡度和坡向

本文根据山东省DEM图获取坡度、坡向图,使用了三种方式:Python GDAL工具自带的函数处理、Python中自己编写函数实现和arcgis中实现。

一.Python中实现(针对TIF格式的DEM数据

1.利用gdal工具处理

(1)代码

  1. from osgeo import gdal, osr
  2. # 获取影像信息
  3. infoDEM = gdal.Info(r"D:\ProfessionalProfile\DEMdata\2_OutputArcMap\demsd0330UTM.tif")
  4. # 计算坡度、坡向
  5. slope = gdal.DEMProcessing(r'D:\ProfessionalProfile\DEMdata\6_DEMXuanRan\slopePy.tif',
  6. r"D:\ProfessionalProfile\DEMdata\2_OutputArcMap\demsd0330UTM.tif", "slope")
  7. aspect = gdal.DEMProcessing(r'D:\ProfessionalProfile\DEMdata\6_DEMXuanRan\aspectPy.tif',
  8. r"D:\ProfessionalProfile\DEMdata\2_OutputArcMap\demsd0330UTM.tif", "aspect", format='GTiff',
  9. trigonometric=0, zeroForFlat=1)

(2)结果

                                                                                            原图

                                                                                     slope图

                                                                                          aspect图

2.参考了网上的部分资料,自己写了一下利用DEM.tif格式的DEM数据获取坡度坡向。

(1)代码

大概分为几个步骤:读取DEM影像——计算梯度——计算坡度坡向——输出TIF影像,每一步都对应着相应的函数。

  1. from osgeo import gdal,ogr,osr
  2. import numpy as np
  3. import math
  4. import datetime
  5. # 读取TIFF遥感影像
  6. def read_img(filename):
  7. dataset = gdal.Open(filename) # 打开文件
  8. # dataset = gdal.Open(r'D:\ProfessionalProfile\DEMdata\slopeAspectPython0322\test.tif')
  9. im_width = dataset.RasterXSize # 栅格矩阵的列数
  10. im_height = dataset.RasterYSize # 栅格矩阵的行数
  11. im_bands = dataset.RasterCount # 波段数
  12. im_geotrans = dataset.GetGeoTransform() # 仿射矩阵,左上角像素的大地坐标和像素分辨率
  13. im_proj = dataset.GetProjection() # 地图投影信息,字符串表示
  14. im_data = dataset.ReadAsArray(0, 0, im_width, im_height)
  15. datatype = im_data.dtype
  16. del dataset # 关闭对象dataset,释放内存
  17. return im_data, im_proj, im_geotrans, im_width, im_height, im_bands, datatype
  18. # 为便于后续坡度计算,需要在原图像的周围添加一圈数值
  19. def AddRound(npgrid):
  20. nx, ny = npgrid.shape[0], npgrid.shape[1] # ny:行数,nx:列数;此处注意顺序
  21. # np.zeros()返回来一个给定形状和类型的用0填充的数组;
  22. zbc=np.zeros((nx+2,ny+2))
  23. # 填充原数据数组
  24. zbc[1:-1,1:-1]=npgrid
  25. #四边填充数据
  26. zbc[0,1:-1]=npgrid[0,:] #上边;0行,所有列;
  27. zbc[-1,1:-1]=npgrid[-1,:] #下边;最后一行,所有列;
  28. zbc[1:-1,0]=npgrid[:,0] #左边;所有行,0列。
  29. zbc[1:-1,-1]=npgrid[:,-1] #右边;所有行,最后一列
  30. #填充剩下四个角点值
  31. zbc[0,0]=npgrid[0,0]
  32. zbc[0,-1]=npgrid[0,-1]
  33. zbc[-1,0]=npgrid[-1,0]
  34. zbc[-1,-1]=npgrid[-1,0]
  35. return zbc
  36. #计算xy方向的梯度
  37. def Cacdxdy(npgrid,sizex,sizey):
  38. nx, ny = npgrid.shape
  39. s_dx = np.zeros((nx,ny))
  40. s_dy = np.zeros((nx,ny))
  41. a_dx = np.zeros((nx, ny))
  42. a_dy = np.zeros((nx, ny))
  43. # 忘记加range报错:object is not iterable
  44. # 坡度、坡向变化率的计算:https://help.arcgis.com/zh-cn/arcgisdesktop/10.0/help/index.html#/na/009z000000vz000000/
  45. for i in range(1,nx-1):
  46. for j in range(1,ny-1):
  47. s_dx[i,j] = ((npgrid[i-1,j+1]+2*npgrid[i,j+1]+npgrid[i+1,j+1])-(npgrid[i-1,j-1]+2*npgrid[i,j-1]+npgrid[i+1,j-1])) / (8 * sizex)
  48. s_dy[i, j] = ((npgrid[i+1, j-1] + 2 * npgrid[i+1, j] + npgrid[i+1,j+1])-(npgrid[i-1,j-1]+2 * npgrid[i-1,j] + npgrid[i-1,j+1])) / (8 * sizey)
  49. a_dx=s_dx*sizex
  50. a_dy=s_dy*sizey
  51. # 保留原数据区域的梯度值
  52. s_dx = s_dx[1:-1,1:-1]
  53. s_dy = s_dy[1:-1,1:-1]
  54. a_dx = a_dx[1:-1, 1:-1]
  55. a_dy = a_dy[1:-1, 1:-1]
  56. # np.savetxt(r"D:\ProfessionalProfile\DEMdata\slopeAspectPython0322\1dxdy.csv",dx,delimiter=",")
  57. return s_dx,s_dy,a_dx,a_dy
  58. #计算坡度/坡向
  59. def CacSlopAsp(s_dx,s_dy,a_dx,a_dy):
  60. # 坡度
  61. slope=(np.arctan(np.sqrt(s_dx*s_dx+s_dy*s_dy)))*180/math.pi #转换成°
  62. #坡向
  63. # #出错:TypeError: only size-1 arrays can be converted to Python scalars
  64. # a2 = math.atan2(a_dy,-a_dx)*180/math.pi
  65. a=np.zeros((a_dy.shape[0],a_dy.shape[1]))
  66. for i in range(0,a_dx.shape[0]):
  67. for j in range(0,a_dx.shape[1]):
  68. a[i,j] = math.atan2(a_dy[i,j], -a_dx[i,j]) * 180 / math.pi
  69. # 输出
  70. aspect = a
  71. # 坡向值将根据以下规则转换为罗盘方向值(0 到 360 度):
  72. # https://help.arcgis.com/zh-cn/arcgisdesktop/10.0/help/index.html#/na/009z000000vp000000/
  73. x, y = a.shape[0],a.shape[1]
  74. for m in range(0,x):
  75. for n in range(0,y):
  76. if a[m,n] < 0:
  77. aspect[m,n] = 90-a[m,n]
  78. elif a[m,n] > 90:
  79. aspect[m,n] = 360.0 - a[m,n] + 90.0
  80. else:
  81. aspect[m,n] = 90.0 - a[m,n]
  82. return slope,aspect
  83. # 遥感影像的存储,写GeoTiff文件
  84. def write_img(filename, tar_proj, im_geotrans, im_data, datatype):
  85. # 判断栅格数据的数据类型
  86. if 'int8' in im_data.dtype.name:
  87. datatype = gdal.GDT_Byte
  88. elif 'int16' in im_data.dtype.name:
  89. datatype = gdal.GDT_UInt16
  90. else:
  91. datatype = gdal.GDT_Float32
  92. # 判读数组维数
  93. if len(im_data.shape) == 3:
  94. # 注意数据的存储波段顺序:im_bands, im_height, im_width
  95. im_bands, im_height, im_width = im_data.shape
  96. else:
  97. im_bands, (im_height, im_width) = 1, im_data.shape
  98. # 创建文件时 driver = gdal.GetDriverByName("GTiff"),数据类型必须要指定,因为要计算需要多大内存空间。
  99. driver = gdal.GetDriverByName("GTiff")
  100. dataset = driver.Create(filename, im_width, im_height, im_bands, datatype)
  101. dataset.SetGeoTransform(im_geotrans) # 写入仿射变换参数
  102. dataset.SetProjection(tar_proj) # 写入投影
  103. if im_bands == 1:
  104. dataset.GetRasterBand(1).WriteArray(im_data) # 写入数组数据
  105. else:
  106. for i in range(im_bands):
  107. dataset.GetRasterBand(i + 1).WriteArray(im_data[i])
  108. del dataset
  109. # 定义投影函数(此次运行没有用到)
  110. def SetPro(filename,tar_proj,outputfilename):
  111. ds = gdal.Open(filename)
  112. im_geotrans = ds.GetGeoTransform() # 仿射矩阵信息
  113. im_proj = ds.GetProjection() # 地图投影信息
  114. im_width = ds.RasterXSize # 栅格矩阵的列数
  115. im_height = ds.RasterYSize # 栅格矩阵的行数
  116. im_bands = ds.RasterCount
  117. ds_array = ds.ReadAsArray(0, 0, im_width, im_height) # 获取原数据信息,包括数据类型int16,维度,数组等信息
  118. # 设置数据类型(原图像有负值)
  119. datatype = gdal.GDT_Float32
  120. # 目标投影
  121. img_proj = tar_proj
  122. # 输出影像路径及名称
  123. name = outputfilename
  124. driver = gdal.GetDriverByName("GTiff") # 创建文件驱动
  125. dataset = driver.Create(name, im_width, im_height, im_bands, datatype)
  126. dataset.SetGeoTransform(im_geotrans) # 写入原图像的仿射变换参数
  127. dataset.SetProjection(img_proj) # 写入目标投影
  128. # 写入影像数据
  129. dataset.GetRasterBand(1).WriteArray(ds_array)
  130. del dataset
  131. if __name__ == "__main__":
  132. startime = datetime.datetime.now() # 程序开始时间
  133. # 读取ASTER GDEM遥感影像
  134. demgrid, proj, geotrans, row, column, band, type = read_img(r'D:\ProfessionalProfile\DEMdata\slopeAspectPython0322\SDdem.tif')
  135. oridata = demgrid
  136. # 为计算梯度给影像添加周围一圈数据
  137. demgrid = AddRound(demgrid)
  138. # 梯度计算
  139. dx1,dy1,dx2,dy2 = Cacdxdy(demgrid,30,30)
  140. # 坡度、坡向计算
  141. slope,aspect = CacSlopAsp(dx1,dy1,dx2,dy2)
  142. # 设置要投影的投影信息,此处是WGS84-UTM-50N
  143. tar_proj = '''PROJCS["WGS 84 / UTM zone 50N",
  144. GEOGCS["WGS 84",
  145. DATUM["WGS_1984",
  146. SPHEROID["WGS 84",6378137,298.257223563,
  147. AUTHORITY["EPSG","7030"]],
  148. AUTHORITY["EPSG","6326"]],
  149. PRIMEM["Greenwich",0,
  150. AUTHORITY["EPSG","8901"]],
  151. UNIT["degree",0.01745329251994328,
  152. AUTHORITY["EPSG","9122"]],
  153. AUTHORITY["EPSG","4326"]],
  154. UNIT["metre",1,
  155. AUTHORITY["EPSG","9001"]],
  156. PROJECTION["Transverse_Mercator"],
  157. PARAMETER["latitude_of_origin",0],
  158. PARAMETER["central_meridian",117],
  159. PARAMETER["scale_factor",0.9996],
  160. PARAMETER["false_easting",500000],
  161. PARAMETER["false_northing",0],
  162. AUTHORITY["EPSG","32650"],
  163. AXIS["Easting",EAST],
  164. AXIS["Northing",NORTH]]'''
  165. # 输出TIFF格式遥感影像,并设置投影坐标
  166. slopeT = write_img(r'D:\ProfessionalProfile\DEMdata\slopeAspectPython0322\slopeSDpy0326.tif', tar_proj, geotrans, slope, type)
  167. aspectT = write_img(r'D:\ProfessionalProfile\DEMdata\slopeAspectPython0322\aspectSDpy0326.tif', tar_proj, geotrans, aspect, type)
  168. endtime = datetime.datetime.now() # 程序结束时间
  169. runtime = endtime-startime # 程序运行时间
  170. print('运行时间为: %d 秒' %(runtime.seconds))

运行时间较长,后续需要优化。

(2)运行结果

                                                                                  DEM图像

                                                               输出的slope影像

                                                                                    输出的aspect影像

 

二.在arcgis中计算slope和aspect

(1)先在ENVI中统计一下DEM信息,看到有部分数据小于0.

所以将背景值设为-300,这样的话就不会影响其他信息。

(2)计算坡度

此时计算出来的坡度范围大概都在88-90度之间,这一看就是有问题。后来查了一下,原因是:从地理空间数据云下载的ASTER GDEM影像没有投影坐标,以至于Z值不准确。

(3)在arcgis中进行“重投影”:WGS 84 – UTM 50N

最后得到的结果看起来正常了一些。

slope图

aspect图

 

3.本文主要参考

[1]坡度:https://help.arcgis.com/zh-cn/arcgisdesktop/10.0/help/index.html#/na/009z000000vz000000/

[2]坡向:https://help.arcgis.com/zh-cn/arcgisdesktop/10.0/help/index.html#/na/009z000000vp000000/

[3]博主锃光瓦亮的枕小路:https://blog.csdn.net/weixin_45561357/article/details/106677574

[4]师动,朱奇峰,杨勤科,龙永清.DEM分辨率对坡度算法选择影响的分析[J].山地学报,2020,38(06):935-944.

[5]博主箜_Kong:https://blog.csdn.net/liminlu0314/article/details/8498985?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161657597316780266219174%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=161657597316780266219174&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v1~rank_blog_v1-1-8498985.pc_v1_rank_blog_v1&utm_term=%E5%9D%A1%E5%BA%A6%E5%9D%A1%E5%90%91