MergeMetadata.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. # -*- coding: utf-8 -*-
  2. __author__ = 'wanger'
  3. __description__ = '批量生成栅格数据元数据表格 EXCEL'
  4. __date__ = '2024-11-25'
  5. __copyright__ = '(C) 2024 by siwei'
  6. __revision__ = '1.0'
  7. import os
  8. from osgeo import gdal, osr
  9. import openpyxl
  10. from qgis.core import (
  11. QgsRasterLayer,
  12. QgsVectorLayer,
  13. QgsSpatialIndex,
  14. QgsFeatureRequest,
  15. QgsGeometry
  16. )
  17. # TODO 栅格数据文件夹
  18. folder_path = 'E:\\projects\\遥感技术部需求\\批量生成元数据\\make\\data'
  19. # TODO 元数据输出文件夹
  20. output_path = 'E:\\projects\\遥感技术部需求\\批量生成元数据\\make\\metadata'
  21. # TODO 元数据模板路径
  22. templatepath = 'E:\\projects\\遥感技术部需求\\批量生成元数据\\make\\template\\template.xlsx'
  23. # 获取元数据模板文件所在的目录
  24. templatedirectory = os.path.dirname(os.path.abspath(templatepath))
  25. # TODO 空间分析图层位置 默认在模板文件夹内有一个overlap.shp 点线面要素无所谓
  26. overlapVectorPath = f"{templatedirectory}\\overlap.shp"
  27. # 绑定需要从shp数据提取的信息
  28. # data_gain_date 影像/数据获取时间
  29. # data_resolution 影像分辨率/点云密度
  30. overlapParams = {
  31. "data_resolution": "resolution",
  32. "data_gain_date": "gaindate"
  33. }
  34. # 通过文件路径来获取文件名和文件后缀
  35. def getImageNumberAndFormat(filepath):
  36. # 获取文件名(包括后缀)
  37. file_name = os.path.basename(filepath)
  38. # 获取文件名(不包括后缀)
  39. file_name_no_ext = os.path.splitext(file_name)[0]
  40. # 获取文件后缀
  41. file_ext = os.path.splitext(file_name)[1][1:]
  42. return file_name_no_ext, file_ext
  43. # 通过文件路径获取文件大小
  44. def getFileSize(filepath):
  45. try:
  46. file_size = os.path.getsize(filepath)
  47. size_mb = file_size / (1024 * 1024)
  48. return f"{size_mb:.2f}"
  49. except FileNotFoundError:
  50. print("文件未找到,请检查路径是否正确!")
  51. return 0
  52. # 获取左上角点
  53. def getImageLeftTop(dataset):
  54. # 获取地理变换参数
  55. geotransform = dataset.GetGeoTransform()
  56. if not geotransform:
  57. raise ValueError("无法获取地理变换参数!")
  58. # 左上角像素的坐标(投影坐标系)
  59. x_min = geotransform[0]
  60. y_max = geotransform[3]
  61. # 获取栅格的空间参考(投影坐标系)
  62. spatial_ref = osr.SpatialReference()
  63. spatial_ref.ImportFromWkt(dataset.GetProjection())
  64. # 转换为地理坐标系(经纬度)
  65. if spatial_ref.IsProjected():
  66. # spatial_ref_geo = spatial_ref.CloneGeogCS()
  67. wgs84_crs = osr.SpatialReference()
  68. wgs84_crs.ImportFromEPSG(4326)
  69. transform = osr.CoordinateTransformation(spatial_ref, wgs84_crs)
  70. lon, lat, _ = transform.TransformPoint(x_min, y_max)
  71. else:
  72. lon, lat = x_min, y_max # 如果已经是地理坐标系,直接使用
  73. return {
  74. "lon": lat,
  75. "lat": lon,
  76. "y": int(x_min),
  77. "x": int(y_max)
  78. }
  79. # 通过栅格数据与矢量数据叠加分析 获取相交面积最大的一块 返回要素
  80. def get_largest_overlapping_feature(raster_path, shp_path):
  81. """
  82. 查询与栅格相交的矢量要素中面积最大的一块,并返回指定字段的值。
  83. :param raster_path: 栅格文件路径
  84. :param shp_path: 矢量文件路径
  85. :return: 面积最大的要素
  86. """
  87. # 加载栅格图层
  88. raster_layer = QgsRasterLayer(raster_path, "Raster Layer")
  89. if not raster_layer.isValid():
  90. raise Exception(f"栅格文件无效: {raster_path}")
  91. # 加载矢量图层
  92. vector_layer = QgsVectorLayer(shp_path, "Vector Layer", "ogr")
  93. if not vector_layer.isValid():
  94. raise Exception(f"SHP文件无效: {shp_path}")
  95. # 获取栅格的范围
  96. raster_extent = raster_layer.extent()
  97. # 创建空间索引以加速查询
  98. spatial_index = QgsSpatialIndex(vector_layer.getFeatures())
  99. # 查询与栅格范围相交的要素ID
  100. intersecting_ids = spatial_index.intersects(raster_extent)
  101. # 遍历这些要素并计算相交区域的面积
  102. max_area = 0
  103. largest_feature = None
  104. for fid in intersecting_ids:
  105. feature = vector_layer.getFeature(fid)
  106. intersection = feature.geometry().intersection(QgsGeometry.fromRect(raster_extent))
  107. # 检查是否有有效相交区域
  108. if intersection.isEmpty():
  109. continue
  110. # 计算相交区域的面积
  111. area = intersection.area()
  112. # 找到面积最大的要素
  113. if area > max_area:
  114. max_area = area
  115. largest_feature = feature
  116. if largest_feature:
  117. return largest_feature
  118. else:
  119. return None
  120. # 遍历文件夹中的所有文件
  121. for filename in os.listdir(folder_path):
  122. filepath = os.path.join(folder_path, filename)
  123. # 检查文件扩展名
  124. if filename.endswith('.img') or filename.endswith('.tif'):
  125. dataset = gdal.Open(filepath)
  126. # TODO 读取图号
  127. image_number, image_format = getImageNumberAndFormat(filepath)
  128. file_size = getFileSize(filepath)
  129. if dataset:
  130. # TODO 获取栅格数据的行数、列数
  131. grid_row = dataset.RasterYSize
  132. grid_col = dataset.RasterXSize
  133. # TODO 格网单元尺寸
  134. grid_size = 2
  135. # TODO波段数
  136. band_size = dataset.RasterCount
  137. # TODO 无效值 list 用逗号隔开
  138. nodata_list = []
  139. # TODO 计算高程记录的小数点位数
  140. decimal_number = None
  141. for band_index in range(1, dataset.RasterCount + 1):
  142. band = dataset.GetRasterBand(band_index)
  143. nodata_list.append(band.GetNoDataValue())
  144. stats = band.GetStatistics(True, True) # 获取统计信息
  145. min_value, max_value = stats[0], stats[1]
  146. if min_value is not None and max_value is not None:
  147. # 获取高程小数位数的最大可能值
  148. decimal_number = max(len(str(min_value).split(".")[-1]),
  149. len(str(max_value).split(".")[-1])) if '.' in str(
  150. min_value) else 0
  151. # TODO 获取栅格数据左上角标
  152. cornerpoint = getImageLeftTop(dataset)
  153. # 获取地理信息
  154. geo_transform = dataset.GetGeoTransform()
  155. # 获取投影信息
  156. projection = dataset.GetProjection()
  157. # 获取栅格数据的投影信息
  158. # 创建 SpatialReference 对象并加载投影信息
  159. spatial_ref = osr.SpatialReference()
  160. spatial_ref.ImportFromWkt(projection)
  161. # TODO 投影参数集合
  162. central_meridian = None
  163. ellipsoid_radius = None
  164. ellipsoid_flat = None
  165. geodetic_datum = None
  166. projection_name = None
  167. divided_into_zones = "无分带"
  168. with_number = "无代号"
  169. plane_coordinate_unit = "米"
  170. altitude_name = "正常高"
  171. altitude_benchmark = "1985国家高程基准"
  172. if spatial_ref.IsProjected():
  173. # TODO 中央子午线
  174. central_meridian = spatial_ref.GetProjParm(osr.SRS_PP_CENTRAL_MERIDIAN)
  175. # TODO 椭球长半径
  176. ellipsoid_radius = spatial_ref.GetSemiMajor() # 长半轴
  177. # TODO 椭球扁率
  178. ellipsoid_flat = f"1/{spatial_ref.GetInvFlattening():.3f}" # 反扁率(1/扁率)
  179. # TODO 所采用大地基准
  180. geodetic_datum = spatial_ref.GetAttrValue("DATUM")
  181. # TODO 地图投影名称
  182. projection_name = spatial_ref.GetAttrValue("PROJECTION")
  183. # TODO 分带方式(适用于 UTM)
  184. if geodetic_datum == "China_2000":
  185. if projection.__contains__("-degree"):
  186. number = projection.split("-degree")[0][-1:]
  187. divided_into_zones = f'{number}度带'
  188. with_number = int(central_meridian / int(number))
  189. elif spatial_ref.IsProjected() and "UTM" in projection:
  190. zone = spatial_ref.GetUTMZone()
  191. divided_into_zones = "UTM分带" if zone else "其他"
  192. with_number = zone if zone else "N/A"
  193. # TODO 平面坐标单位
  194. plane_coordinate_unit = spatial_ref.GetLinearUnitsName()
  195. linear_units = spatial_ref.GetLinearUnits() # 单位与米的换算关系
  196. # 高程系统(如果有)
  197. altitude_name = spatial_ref.GetAttrValue("VERT_CS")
  198. altitude_benchmark = spatial_ref.GetAttrValue("VERT_DATUM")
  199. metadatadict = {
  200. "image_number": image_number,
  201. "data_format": image_format,
  202. "data_volume": file_size,
  203. "grid_size": grid_size,
  204. "grid_row": grid_row,
  205. "grid_col": grid_col,
  206. "decimal_number": decimal_number,
  207. "nodata_number": ",".join(map(str, nodata_list)),
  208. "cornerpoint_lon": cornerpoint["lon"],
  209. "cornerpoint_lat": cornerpoint["lat"],
  210. "cornerpoint_x": cornerpoint["x"],
  211. "cornerpoint_y": cornerpoint["y"],
  212. "ellipsoid_radius": ellipsoid_radius,
  213. "ellipsoid_flat": ellipsoid_flat,
  214. "geodetic_datum": "2000国家大地坐标系" if geodetic_datum == "China_2000" else geodetic_datum,
  215. "projection_name": "高斯-克吕格投影" if projection_name == "Transverse_Mercator" else projection_name,
  216. "central_meridian": central_meridian,
  217. "divided_into_zones": divided_into_zones,
  218. "with_number": with_number,
  219. "plane_coordinate_unit": "米" if plane_coordinate_unit == "metre" else plane_coordinate_unit,
  220. "altitude_name": altitude_name,
  221. "altitude_benchmark": altitude_benchmark
  222. }
  223. # TODO 空间叠加
  224. for key in overlapParams:
  225. value = overlapParams[key]
  226. largest_feature = get_largest_overlapping_feature(raster_path=filepath, shp_path=overlapVectorPath)
  227. field_value = largest_feature[value]
  228. metadatadict[key] = field_value
  229. # 利用模板生成excel
  230. workbook = openpyxl.load_workbook(templatepath)
  231. sheet = workbook.active
  232. # 定义替换数据的字典
  233. replacements = {}
  234. for attr in metadatadict:
  235. key = f"<{attr}>"
  236. value = metadatadict[attr]
  237. replacements[key] = value
  238. # 遍历单元格,替换占位符
  239. for row in sheet.iter_rows():
  240. for cell in row:
  241. if isinstance(cell.value, str): # 确保单元格内容是字符串
  242. for placeholder, replacement in replacements.items():
  243. if placeholder in cell.value:
  244. # 替换占位符
  245. cell.value = cell.value.replace(placeholder, str(replacement))
  246. # 保存修改后的文件
  247. workbook.save(f'{output_path}\\{image_number}.xlsx')