本文介绍基于Python 中GDAL 模块,批量将大量.nc
格式的栅格文件转换为.tif
格式,并解决可能出现的转换后图像颠倒、镜像、翻转等问题。
最近,需要批量将大量.nc
格式的栅格文件转换为.tif
格式。如下图所示,有多个待转换的.nc
格式文件,且对于每一个.nc
格式文件,其都含有多个时相的数据。

其实,对于.nc
格式转.tif
格式,除了gdal
库之外,还有其他非常多成熟、方便的Python 库或R 语言库可以实现,且这些库都比gdal
库用起来方便------甚至安装过程也比gdal
库方便;但是,我在一开始用这些其他库进行格式转换时发现:对于我的.nc
格式数据,若用其他库进行转换,最终得到的.tif
图像并不是正确的------其要么是行列数(也就是经纬度)都反了,要么是经度或纬度其中一个是反着的;包括Python 的netCDF4
库、xarray
库、rioxarray
库,以及R 语言的ncdf
库等,都存在这个问题。
如下图所示,这个就是我遇到的其中一种情况。可以看到,这个结果数据的经度 倒是没错,但是纬度搞反了,所以全球的图像是反着来的,南极跑到北极去了。

针对这种情况,我大致看了一下原本的.nc
格式数据,感觉问题应该就是出在.nc
数据上------比如可能对于一些学者自行生产的科学数据产品,其在数据生产完毕、封装为.nc
格式时,经纬度是反着写的;而上述提及的那些成熟的.nc
格式转.tif
格式的库,他们默认是正着解析经度和纬度的,所以出现了上述问题。
所以,为了解决这个问题,那就不太好用这些封装好的.nc
格式转.tif
格式库了,而是需要将.nc
格式数据直接提取为类似于array
格式的矩阵数据,然后手动进行矩阵变换,再将其导出为.tif
------那这样的话,就只有gdal
库符合要求了。
本文所用代码如下。
python
import os
import numpy as np
import netCDF4 as nc
from osgeo import gdal
from osgeo import osr
nc_folder = R"e:\LTDR\N07"
tif_folder = R"d:\99_Temp\NDVI\SPEI\TIFF"
data_name = "spei"
nodata = -9999
scale = 10000
res = 0.25
if not os.path.exists(tif_folder):
os.makedirs(tif_folder)
driver = gdal.GetDriverByName('GTiff')
for nc_file in os.listdir(nc_folder):
if nc_file.endswith(".nc"):
nc_file_path = os.path.join(nc_folder, nc_file)
year = nc_file.split("_")[2]
with nc.Dataset(nc_file_path) as file:
data = np.array(file[data_name][:])
times = np.array(file['time'][:])
lats = np.array(file['lat'][:])
lons = np.array(file['lon'][:])
lat_min, lat_max = lats.min(), lats.max()
lon_min, lon_max = lons.min(), lons.max()
lat_num = len(lats)
lon_num = len(lons)
lat_res = res
lon_res = res
lat_origin = lat_max + lat_res / 2
lon_origin = lon_min - lon_res / 2
for time in range(len(times)):
daily_data = data[time, :, :]
# scale the data if necessary
# daily_data = np.round(data[time, :, :] * scale).astype(np.int32)
# Flip the data to match the expected orientation, here are three options:
daily_data = np.flipud(daily_data.T)
daily_data = np.flipud(daily_data)
daily_data = daily_data.T
output_tif_path = os.path.join(
tif_folder,
f"SPEI_{year}{str(time + 1).zfill(3)}.tif"
)
outRaster = driver.Create(output_tif_path, lon_num, lat_num, 1, gdal.GDT_Float32)
# outRaster = driver.Create(output_tif_path, lon_num, lat_num, 1, gdal.GDT_Int32)
outRaster.SetGeoTransform([lon_origin, lon_res, 0, lat_origin, 0, -lat_res])
sr = osr.SpatialReference()
sr.SetWellKnownGeogCS('WGS84')
outRaster.SetProjection(sr.ExportToWkt())
band = outRaster.GetRasterBand(1)
band.SetNoDataValue(nodata)
# scale the data if necessary
# band.SetNoDataValue(nodata * scale)
band.WriteArray(daily_data)
print(output_tif_path, ' finished')
del outRaster
其中,nc_folder
就是.nc
格式文件所在目录,tif_folder
是输出.tif
格式文件的目录,data_name
是要提取的变量名,nodata
表示填充值,scale
是缩放系数(例如假设原本的.nc
格式文件是浮点数,乘以10000
并取整后,存为整数可节省空间),res
则是空间分辨率。
代码整体思路也很简单,主要就是需要关注daily_data = np.flipud(daily_data.T)
这句代码以及其下方的2
行代码。这里就是将原本.nc
格式文件数据加以变换的地方,这里列出了3
种变换方法,分别为先转置、后上下颠倒 ,以及直接上下颠倒 ,还有直接转置 。这里之所以列出3
种方法,是因为我当时要转换的.nc
格式数据产品有很多种,为了方便就将不同种对我有效果的变换方法都写上了;大家使用代码时,需要注意选择自己适合的变换方法。还有一种情况,就是可能图像还会出现左右颠倒 的问题,也就是纬度没问题、但经度反了------不过这种情况感觉一般不会遇到,所以当时就没写变换的代码;如果大家遇到了,那就需要额外对array
进行左右变换。
至此,大功告成。
欢迎关注:疯狂学习GIS