TIFF裁切

步骤如下:

  1. 读取xml:读取tiff对应的xml文件,得到tiff文件的经纬度坐标和wkt范围;
  2. 相交分析:将用户输入的wkt范围与tiff源文件范围进行相交,得到新的wkt范围;
  3. wkt转shp:将相交分析后的wkt转为shp文件;
  4. rpc校正:某些tiff中缺少坐标系信息,故需要进行rpc校正,将rpb文件中的信息嵌入到tiff中,得到新的tiff;
  5. tiff裁切:用shp去裁切rpc校正后的tiff;
  6. 生成缩略图:tiff转图片,图片格式为jpg或png。

读取xml

def parse_xml(eles):
    """
    适用于除GF3以外的tiff以及部分H1D类型的h5
    :param eles: 文档元素树
    :return: wkt_range: wkt范围
    """
    TopLeftLatitude = eles.getElementsByTagName("TopLeftLatitude")[0].childNodes[0].data  # 左上 纬度
    TopLeftLongitude = eles.getElementsByTagName("TopLeftLongitude")[0].childNodes[0].data  # 左上 经度
    TopRightLatitude = eles.getElementsByTagName("TopRightLatitude")[0].childNodes[0].data  # 右上 纬度
    TopRightLongitude = eles.getElementsByTagName("TopRightLongitude")[0].childNodes[0].data  # 右上 经度
    BottomRightLatitude = eles.getElementsByTagName("BottomRightLatitude")[0].childNodes[0].data  # 右下 纬度
    BottomRightLongitude = eles.getElementsByTagName("BottomRightLongitude")[0].childNodes[0].data  # 右下经度
    BottomLeftLatitude = eles.getElementsByTagName("BottomLeftLatitude")[0].childNodes[0].data  # 左下 纬度
    BottomLeftLongitude = eles.getElementsByTagName("BottomLeftLongitude")[0].childNodes[0].data  # 左下 经度

    wkt_range = f"POLYGON (({TopLeftLongitude} {TopLeftLatitude},{BottomLeftLongitude} {BottomLeftLatitude}," \
                f"{BottomRightLongitude} {BottomRightLatitude},{TopRightLongitude} {TopRightLatitude}," \
                f"{TopLeftLongitude} {TopLeftLatitude}))"
    min_lon = min(TopLeftLongitude, BottomLeftLongitude, BottomRightLongitude, TopRightLongitude)
    max_lon = max(TopLeftLongitude, BottomLeftLongitude, BottomRightLongitude, TopRightLongitude)
    min_lat = min(TopLeftLatitude, BottomLeftLatitude, BottomRightLatitude, TopRightLatitude)
    max_lat = max(TopLeftLatitude, BottomLeftLatitude, BottomRightLatitude, TopRightLatitude)
    lon_range = float(max_lon) - float(min_lon)
    lat_range = float(max_lat) - float(min_lat)

    return wkt_range, lon_range, lat_range

相交分析

def xmlIntersect(wkt_tiff, wkt_range):
    """
    相交分析: 将图像范围与输入的待裁剪范围进行相交, 得到交集部分
    :param wkt_tiff: 图像的wkt范围
    :param wkt_range: 待裁切的wkt范围
    :return: intersection.ExportToWkt(): 经纬度范围, 为wkt格式
    """
    poly1 = ogr.CreateGeometryFromWkt(wkt_tiff)  # 图像范围wkt
    poly2 = ogr.CreateGeometryFromWkt(wkt_range)  # 裁剪范围wkt
    intersection = poly1.Intersection(poly2)
    logger.info(f"图像范围: {poly1}")
    logger.info(f"裁剪范围: {poly2}")
    logger.info(f"相交分析成功: {intersection.ExportToWkt()}")
    return intersection.ExportToWkt()

wkt转shp

def wktToShp(wkt_range_intersect, output_shp_file):
    """
    根据wkt字符串生成shp文件, 以便用于裁切
    :param wkt_range_intersect: wkt字符串, 包含经纬度, 根据此范围进行裁切
    :param output_shp_file: 输出的shp文件
    """
    wkt_range_string = wkt.loads(wkt_range_intersect)
    logger.info("根据wkt生成矢量")
    points = geopandas.GeoSeries(wkt_range_string, crs='EPSG:4326')  # 坐标系: WGS 1984
    gdal.SetConfigOption("GTIFF_SRS_SOURCE", "EPSG")
    points.to_file(output_shp_file, driver="ESRI Shapefile", encoding='utf-8')
    logger.info(f"根据wkt生成矢量成功: {output_shp_file}")

rpc校正

def rpcCorrect(input_tiff_file, lon_range, lat_range, output_rpc_file):
    """
    原tiff文件没有坐标系及相关信息, 故需要进行rpc校正
    读取tiff文件路径下的.rpb文件, 输出新的tiff文件, 带有坐标系信息
    若无rpb文件则不进行rpc校正, 继续后续裁切
    param input_tiff: 输入的原始影像
    param output_tiff: rpc校正后输出的正射影像
    """
    logger.info(f"开始进行rpc校正: {input_tiff_file}")
    dataset = gdal.Open(input_tiff_file, gdal.GA_Update)  # 读入影像
    rpc = dataset.GetMetadata("RPC")  # 读入影像,rpc
    # 设置分辨率
    x_size, y_size = dataset.RasterXSize, dataset.RasterYSize
    x_res = lon_range / x_size
    y_res = lat_range / y_size

    if rpc != {}:
        gdal.Warp(output_rpc_file, dataset, dstSRS='EPSG:4326',
                  xRes=x_res,  # x方向正射影像分辨率
                  yRes=y_res,  # y方向正射影像分辨率
                  resampleAlg=gdal.GRIORA_Bilinear,  # 插值方式,默认为最邻近,我们一般使用双线性
                  rpc=True,  # 使用RPC模型进行校正
                  )
        dataset = None  # 关闭文件
        logger.info(f"rpc校正成功: {output_rpc_file}")
    else:
        logger.info("读取rpc为空,跳过rpc校正")

tiff裁切

def cutTiff(tiff_file, shp_file, cut_tiff_file):
    """
    核心函数: 实现tiff裁切功能
    :param tiff_file: 经过rpc校正后的tiff文件路径
    :param shp_file: 根据wkt生成的shp文件路径
    :param cut_tiff_file: 裁切后的tiff文件路径
    """
    logger.info("开始裁切")
    input_raster = gdal.Open(tiff_file)
    if input_raster is None:
        logger.warning(f"tiff文件无法加载: {tiff_file}")

    gdal.Warp(cut_tiff_file,
              input_raster,
              format='GTiff',
              # dstSRS='EPSG:4326',
              cutlineDSName=shp_file,
              cropToCutline=False,
              # 保证裁剪后影像大小跟矢量文件的图框大小一致(设置为False时,结果图像大小会跟待裁剪影像大小一样,则会出现大量的空值区域)
              copyMetadata=True,
              dstNodata=0,
              creationOptions=["COMPRESS=LZW", "TILED=True", "BIGTIFF=YES"],
              )

    input_raster = None  # 关闭文件
    logger.info(f"裁切成功: {cut_tiff_file}")

生成缩略图

def tiffToPng(tiff_file, png_file, scale=0.3):
    """
    将tiff文件转为png图片
    :param tiff_file: tiff文件路径
    :param png_file: 生成的图片路径
    :param scale: 图像缩放比例
    """
    logger.info("开始生成缩略图")
    tiff_name = os.path.split(tiff_file)[1]
    img = io.imread(tiff_file)  # 读取文件名
    logger.debug(f"图片尺寸: {img.shape}; 数据类型: {img.dtype}")  # 显示图片大小和类型
    # GF3类型部分文件变量类型 float16 溢出,需要调整类型为 float32
    if tiff_name.startswith("GF3"):
        my_type = np.float32
    else:
        my_type = np.float16
    logger.debug(f"ndarray的数据类型: {my_type}")
    if len(img.shape) == 3:
        img = np.divide(img[:, :, 0:3], img.max(), dtype=my_type)  # 使其所有值不大于一
        logger.debug(f"处理后的图片尺寸: {img.shape}")
        # 几乎全黑,解决色差
        if tiff_name.startswith("GF3_KSC_FSI") or tiff_name.startswith("GF3_KRN_SS"):
            img = img * 2550
        elif tiff_name.startswith("GF3_SYC_QPSI"):
            img = img * 30000
        else:
            img = img * 255
        img = img.astype(np.uint8)  # 强制转换成8位整型
        # 得到图片长宽高
        height, width, _ = img.shape
        # 图片像素点太多,网页无法正常显示,需要减小像素点数量
        img_resize = cv2.resize(img, dsize=(round(width * scale), round(height * scale)))
        b = img_resize[:, :, 0]    # 读取蓝通道
        g = img_resize[:, :, 1]    # 读取绿通道
        r = img_resize[:, :, 2]    # 读取红通道
        rgb_img = cv2.merge([b, g, r])  # 通道拼接
        # 三通道转四通道,设置黑色为透明
        gray_image = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)
        _, alpha = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY)
        rgba_image = cv2.merge([b, g, r, alpha], 4)
        # 识别轮廓,裁剪图像
        contours, _ = cv2.findContours(alpha, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        x, y, w, h = cv2.boundingRect(contours[0])
        # 绘制轮廓
        cv2.drawContours(rgba_image, contours, 0, (0, 0, 255), 5)
        # 提取图像范围
        cropped_image = rgba_image[y:y + h, x:x + w]
        # 保存图像
        cv2.imwrite(png_file, cropped_image, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
    elif len(img.shape) == 2:
        img = np.divide(img, img.max(), dtype=my_type)  # 使其所有值不大于一
        logger.debug(f"处理后的图片尺寸: {img.shape}")
        # 部分GF3类型存在问题: 几乎全黑,解决色差
        if tiff_name.startswith("GF3_KSC_FSI") or tiff_name.startswith("GF3_KRN_SS"):
            img = img * 2550
        elif tiff_name.startswith("GF3_SYC_QPSI"):
            img = img * 30000
        else:
            img = img * 255
        img = img.astype(np.uint8)  # 强制转换成8位整型
        # 得到图片的高度、宽度
        height, width = img.shape
        img_resize = cv2.resize(img, dsize=(round(width * scale), round(height * scale)))
        rgb_image = cv2.cvtColor(img_resize, cv2.COLOR_GRAY2RGB)
        b = rgb_image[:, :, 0]  # 读取蓝通道
        g = rgb_image[:, :, 1]  # 读取绿通道
        r = rgb_image[:, :, 2]  # 读取红通道
        _, alpha = cv2.threshold(img_resize, 0, 255, cv2.THRESH_BINARY)
        rgba_image = cv2.merge([b, g, r, alpha], 4)
        # 识别轮廓,裁剪图像
        contours, _ = cv2.findContours(alpha, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        x, y, w, h = cv2.boundingRect(contours[0])
        # 绘制轮廓
        cv2.drawContours(rgba_image, contours, 0, (0, 0, 255), 5)
        # 提取图像范围
        cropped_image = rgba_image[y:y + h, x:x + w]
        # 保存图像
        cv2.imwrite(png_file, cropped_image, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])

    try:
        del img, b, g, r, rgba_image  # 释放内存
        gc.collect()
        logger.debug(f"释放内存成功,删除变量: img, b, g, r, rgba_image")
    except Exception as e:
        logger.error(e)
    logger.info(f"生成缩略图成功: {png_file}")

参考文章

  1. 一般使用 - python GDAL/OGR 中文手册 (luolingchun.github.io)
  2. 2. GDAL python教程(1)——用OGR读写矢量数据 — 开放地理空间实验室: 犹他州立大学——开源GIS类库GDAL资料:Python GDAL课程笔记 (osgeo.cn)
  3. 30分钟学会shapely空间几何分析_shapely空间计算近邻分析-CSDN博客
  4. 用Python剪裁栅格数据,以及如何用Python读取shapefile - 简书 (jianshu.com)
  5. python批量遥感影像裁剪的三种方法(gdal.Warp,arcpy)-CSDN博客
  6. python-gdal-test: 基于Python-GDAL的实践总结 (gitee.com)
  7. Polygon—ArcGIS Pro | 文档
  8. GTiff—GeoTIFF文件格式 — GDAL 文档 (osgeo.cn)
  9. Python与开源GIS (osgeo.cn)
posted @ 2024-01-23 08:55  Alphapy  阅读(26)  评论(0)    收藏  举报