123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- # -*- coding: utf-8 -*-
- __author__ = 'wanger'
- __description__ = '批量生成栅格数据元数据表格 EXCEL'
- __date__ = '2024-11-25'
- __copyright__ = '(C) 2024 by siwei'
- __revision__ = '1.0'
- import os
- from osgeo import gdal, osr
- import openpyxl
- from qgis.core import (
- QgsRasterLayer,
- QgsVectorLayer,
- QgsSpatialIndex,
- QgsFeatureRequest,
- QgsGeometry
- )
- # TODO 栅格数据文件夹
- folder_path = 'E:\\projects\\遥感技术部需求\\批量生成元数据\\make\\data'
- # TODO 元数据输出文件夹
- output_path = 'E:\\projects\\遥感技术部需求\\批量生成元数据\\make\\metadata'
- # TODO 元数据模板路径
- templatepath = 'E:\\projects\\遥感技术部需求\\批量生成元数据\\make\\template\\template.xlsx'
- # 获取元数据模板文件所在的目录
- templatedirectory = os.path.dirname(os.path.abspath(templatepath))
- # TODO 空间分析图层位置 默认在模板文件夹内有一个overlap.shp 点线面要素无所谓
- overlapVectorPath = f"{templatedirectory}\\overlap.shp"
- # 绑定需要从shp数据提取的信息
- # data_gain_date 影像/数据获取时间
- # data_resolution 影像分辨率/点云密度
- overlapParams = {
- "data_resolution": "resolution",
- "data_gain_date": "gaindate"
- }
- # 通过文件路径来获取文件名和文件后缀
- def getImageNumberAndFormat(filepath):
- # 获取文件名(包括后缀)
- file_name = os.path.basename(filepath)
- # 获取文件名(不包括后缀)
- file_name_no_ext = os.path.splitext(file_name)[0]
- # 获取文件后缀
- file_ext = os.path.splitext(file_name)[1][1:]
- return file_name_no_ext, file_ext
- # 通过文件路径获取文件大小
- def getFileSize(filepath):
- try:
- file_size = os.path.getsize(filepath)
- size_mb = file_size / (1024 * 1024)
- return f"{size_mb:.2f}"
- except FileNotFoundError:
- print("文件未找到,请检查路径是否正确!")
- return 0
- # 获取左上角点
- def getImageLeftTop(dataset):
- # 获取地理变换参数
- geotransform = dataset.GetGeoTransform()
- if not geotransform:
- raise ValueError("无法获取地理变换参数!")
- # 左上角像素的坐标(投影坐标系)
- x_min = geotransform[0]
- y_max = geotransform[3]
- # 获取栅格的空间参考(投影坐标系)
- spatial_ref = osr.SpatialReference()
- spatial_ref.ImportFromWkt(dataset.GetProjection())
- # 转换为地理坐标系(经纬度)
- if spatial_ref.IsProjected():
- # spatial_ref_geo = spatial_ref.CloneGeogCS()
- wgs84_crs = osr.SpatialReference()
- wgs84_crs.ImportFromEPSG(4326)
- transform = osr.CoordinateTransformation(spatial_ref, wgs84_crs)
- lon, lat, _ = transform.TransformPoint(x_min, y_max)
- else:
- lon, lat = x_min, y_max # 如果已经是地理坐标系,直接使用
- return {
- "lon": lat,
- "lat": lon,
- "y": int(x_min),
- "x": int(y_max)
- }
- # 通过栅格数据与矢量数据叠加分析 获取相交面积最大的一块 返回要素
- def get_largest_overlapping_feature(raster_path, shp_path):
- """
- 查询与栅格相交的矢量要素中面积最大的一块,并返回指定字段的值。
- :param raster_path: 栅格文件路径
- :param shp_path: 矢量文件路径
- :return: 面积最大的要素
- """
- # 加载栅格图层
- raster_layer = QgsRasterLayer(raster_path, "Raster Layer")
- if not raster_layer.isValid():
- raise Exception(f"栅格文件无效: {raster_path}")
- # 加载矢量图层
- vector_layer = QgsVectorLayer(shp_path, "Vector Layer", "ogr")
- if not vector_layer.isValid():
- raise Exception(f"SHP文件无效: {shp_path}")
- # 获取栅格的范围
- raster_extent = raster_layer.extent()
- # 创建空间索引以加速查询
- spatial_index = QgsSpatialIndex(vector_layer.getFeatures())
- # 查询与栅格范围相交的要素ID
- intersecting_ids = spatial_index.intersects(raster_extent)
- # 遍历这些要素并计算相交区域的面积
- max_area = 0
- largest_feature = None
- for fid in intersecting_ids:
- feature = vector_layer.getFeature(fid)
- intersection = feature.geometry().intersection(QgsGeometry.fromRect(raster_extent))
- # 检查是否有有效相交区域
- if intersection.isEmpty():
- continue
- # 计算相交区域的面积
- area = intersection.area()
- # 找到面积最大的要素
- if area > max_area:
- max_area = area
- largest_feature = feature
- if largest_feature:
- return largest_feature
- else:
- return None
- # 遍历文件夹中的所有文件
- for filename in os.listdir(folder_path):
- filepath = os.path.join(folder_path, filename)
- # 检查文件扩展名
- if filename.endswith('.img') or filename.endswith('.tif'):
- dataset = gdal.Open(filepath)
- # TODO 读取图号
- image_number, image_format = getImageNumberAndFormat(filepath)
- file_size = getFileSize(filepath)
- if dataset:
- # TODO 获取栅格数据的行数、列数
- grid_row = dataset.RasterYSize
- grid_col = dataset.RasterXSize
- # TODO 格网单元尺寸
- grid_size = 2
- # TODO波段数
- band_size = dataset.RasterCount
- # TODO 无效值 list 用逗号隔开
- nodata_list = []
- # TODO 计算高程记录的小数点位数
- decimal_number = None
- for band_index in range(1, dataset.RasterCount + 1):
- band = dataset.GetRasterBand(band_index)
- nodata_list.append(band.GetNoDataValue())
- stats = band.GetStatistics(True, True) # 获取统计信息
- min_value, max_value = stats[0], stats[1]
- if min_value is not None and max_value is not None:
- # 获取高程小数位数的最大可能值
- decimal_number = max(len(str(min_value).split(".")[-1]),
- len(str(max_value).split(".")[-1])) if '.' in str(
- min_value) else 0
- # TODO 获取栅格数据左上角标
- cornerpoint = getImageLeftTop(dataset)
- # 获取地理信息
- geo_transform = dataset.GetGeoTransform()
- # 获取投影信息
- projection = dataset.GetProjection()
- # 获取栅格数据的投影信息
- # 创建 SpatialReference 对象并加载投影信息
- spatial_ref = osr.SpatialReference()
- spatial_ref.ImportFromWkt(projection)
- # TODO 投影参数集合
- central_meridian = None
- ellipsoid_radius = None
- ellipsoid_flat = None
- geodetic_datum = None
- projection_name = None
- divided_into_zones = "无分带"
- with_number = "无代号"
- plane_coordinate_unit = "米"
- altitude_name = "正常高"
- altitude_benchmark = "1985国家高程基准"
- if spatial_ref.IsProjected():
- # TODO 中央子午线
- central_meridian = spatial_ref.GetProjParm(osr.SRS_PP_CENTRAL_MERIDIAN)
- # TODO 椭球长半径
- ellipsoid_radius = spatial_ref.GetSemiMajor() # 长半轴
- # TODO 椭球扁率
- ellipsoid_flat = f"1/{spatial_ref.GetInvFlattening():.3f}" # 反扁率(1/扁率)
- # TODO 所采用大地基准
- geodetic_datum = spatial_ref.GetAttrValue("DATUM")
- # TODO 地图投影名称
- projection_name = spatial_ref.GetAttrValue("PROJECTION")
- # TODO 分带方式(适用于 UTM)
- if geodetic_datum == "China_2000":
- if projection.__contains__("-degree"):
- number = projection.split("-degree")[0][-1:]
- divided_into_zones = f'{number}度带'
- with_number = int(central_meridian / int(number))
- elif spatial_ref.IsProjected() and "UTM" in projection:
- zone = spatial_ref.GetUTMZone()
- divided_into_zones = "UTM分带" if zone else "其他"
- with_number = zone if zone else "N/A"
- # TODO 平面坐标单位
- plane_coordinate_unit = spatial_ref.GetLinearUnitsName()
- linear_units = spatial_ref.GetLinearUnits() # 单位与米的换算关系
- # 高程系统(如果有)
- altitude_name = spatial_ref.GetAttrValue("VERT_CS")
- altitude_benchmark = spatial_ref.GetAttrValue("VERT_DATUM")
- metadatadict = {
- "image_number": image_number,
- "data_format": image_format,
- "data_volume": file_size,
- "grid_size": grid_size,
- "grid_row": grid_row,
- "grid_col": grid_col,
- "decimal_number": decimal_number,
- "nodata_number": ",".join(map(str, nodata_list)),
- "cornerpoint_lon": cornerpoint["lon"],
- "cornerpoint_lat": cornerpoint["lat"],
- "cornerpoint_x": cornerpoint["x"],
- "cornerpoint_y": cornerpoint["y"],
- "ellipsoid_radius": ellipsoid_radius,
- "ellipsoid_flat": ellipsoid_flat,
- "geodetic_datum": "2000国家大地坐标系" if geodetic_datum == "China_2000" else geodetic_datum,
- "projection_name": "高斯-克吕格投影" if projection_name == "Transverse_Mercator" else projection_name,
- "central_meridian": central_meridian,
- "divided_into_zones": divided_into_zones,
- "with_number": with_number,
- "plane_coordinate_unit": "米" if plane_coordinate_unit == "metre" else plane_coordinate_unit,
- "altitude_name": altitude_name,
- "altitude_benchmark": altitude_benchmark
- }
- # TODO 空间叠加
- for key in overlapParams:
- value = overlapParams[key]
- largest_feature = get_largest_overlapping_feature(raster_path=filepath, shp_path=overlapVectorPath)
- field_value = largest_feature[value]
- metadatadict[key] = field_value
- # 利用模板生成excel
- workbook = openpyxl.load_workbook(templatepath)
- sheet = workbook.active
- # 定义替换数据的字典
- replacements = {}
- for attr in metadatadict:
- key = f"<{attr}>"
- value = metadatadict[attr]
- replacements[key] = value
- # 遍历单元格,替换占位符
- for row in sheet.iter_rows():
- for cell in row:
- if isinstance(cell.value, str): # 确保单元格内容是字符串
- for placeholder, replacement in replacements.items():
- if placeholder in cell.value:
- # 替换占位符
- cell.value = cell.value.replace(placeholder, str(replacement))
- # 保存修改后的文件
- workbook.save(f'{output_path}\\{image_number}.xlsx')
|