Fork me on GitHub

GDAL笔记--chapter11

本章主要介绍了利用numpy和scipy库做地图运算,并讲述了局部分析、焦点分析、区域分析和全局分析几种不同的方法。

1.保存为新栅格

import gdal
import numpy as np
#保存为新栅格的函数
def make_raster(in_ds, fn, data, data_type, nodata=None):
    driver = gdal.GetDriverByName('GTiff')
    out_ds = driver.Create(fn, in_ds.RasterXSize, in_ds.RasterYSize, 1, data_type)
    out_ds.SetProjection(in_ds.GetProjection())
    out_ds.SetGeoTransform(in_ds.GetGeoTransform())
    out_band = out_ds.GetRasterBand(1)
    if nodata is not None:
        out_band.SetNoDataValue(nodata)
    out_band.WriteArray(data)
    out_band.FlushCache()
    out_band.ComputeStatistics(False)
    return out_ds

2.局部分析(多栅格运算)

#检查np.nan\np.inf
ndvi = (nir - red)/(nir + red)
ndvi = np.where(np.isnan(ndvi), -99, ndvi)
ndvi = np.where(np.isinf(ndvi), -99, ndvi)
out_band.WriteArray(ndvi)
out_band.SetNoDataValue(-99)  #设置-99为nodata!!!

ndvi = np.where(nir+red > 0, (nir-red)/(nir+red), -99)  #设置分母大于0条件

#给你NAIP图像计算NDVI值
import os
import numpy as np
from osgeo import gdal
import ospybook as pb

os.chdir('')
in_fn = ''
out_fn = 'ndvi.tif'

ds = gdal.Open('')
red = ds.GetRasterBand(1).ReadAsArray().astype(np.float) #转float
nir = ds.GetRasterBand(4).ReadAsArray()
red = np.ma.masked_where(nir+red==0, red) #蒙版nir+red==0区域屏蔽红色数组,那么这里不做运算
ndvi = (nir-red)/(nir+red)
ndvi = ndvi.filled(-99)  #对没有运算的单元,进行填充

out_ds = pb.make_raster(ds, out_fn, ndvi, gdal.GDT_Float32, -99)
overviews = pb.compute_overview_levels(out_ds.GetRasterBand(1))
out_ds.BuildOverViews('average', overviews)
del ds, out_ds

#蒙版2(创建一个单独的掩码数组,应用到多个数组)
mask = np.ma.equal(nir+red, 0)
red = np.ma.masked_array(red, mask) #蒙版屏蔽目标区域红色波段

3.从催化剂数组得到任意大小切片数组(这里数组读取是连续的,没有步长;和有步长的切片方法不一样)

#从催化剂数组中得到任意大小切片的函数
def make_slices(data, win_size):
    rows = data.shape[0]-win_size[0]+1
    cols = data.shape[1]-win_size[1]+1
    slices=[]
    for i in range(win_size[0]):
        for j in range(win_size[1]):
            slices.append(data[i:rows+i, j:cols+j])
    return slices

stacked = np.dstack(slices) #堆叠在第三个维度中,返回一个可用于计算均值的三维数组
outdata = np.zeros(indata.shape, np.float32)
outdata[1:-1, 1:-1] = np.mean(stacked, 2)  #在高度维上取平均,由于切片比原图小,每侧切掉一行一列

4.焦点分析(使用围绕的像素计算,先计算切片,再运算)

4.1 平滑高程数据集

#平滑一个高程数据集
import os
import numpy as np
from osgeo import gdal
import ospybook as pb

in_fn=''
out_fn=''
in_ds = gdal.Open(in_fn)
in_band = in_ds.GetRasterBand(1)
in_data = in_band.ReadAsArray()

slices = pb.make_slices(in_data, (3,3))  #(3*3)取切片
stacked_data = np.ma.dstack(slices)  #dstack堆叠数据

rows, cols = in_band.YSize, in_band.XSize
out_data = np.ones((rows, cols), np.int32)*-99   #初始化nodata,保证边缘多出部分为nodata
out_data[1:-1,1:-1] = np.mean(stacked_data, 2)  #取平均需要stack,然后mean

pb.make_raster(in_ds, out_fn, out_data, gdal.GDT_Int32, -99)
del in_ds

4.2 坡度计算

#从DEM计算坡度,这里不必把切片堆叠到三维数组(dstack),因为需要在坡度方程单独引用切片
import os
import numpy as np
from osgeo import gdal
import ospybook as pb

in_fn=''
out_fn=''

in_ds = gdal.Open('')
cell_width = in_ds.GetGeoTransform()[1]  #x方向分辨率
cell_height = in_ds.GetGeoTransform()[5] #y方向分辨率
band = in_ds.GetRasterBand(1)
in_data = band.ReadAsArray().astype(np.float)
out_data = np.ones((band.YSize, band.XSize))*-99 #初始化nodata

slices = pb.make_slices(in_data, (3,3))  #切片
rise = slice[6]+ (2*slices[7] + slices[8] - slices[8]) - (slices[0] + 2*slices[1] +slices[2])/(8*cell_height)
run = ...  #计算偏导数
dist = np.sqrt(np.square(rise)+np.square(run))
out_data[1:-1, 1:-1] = np.arctan(dist)*180/np.pi

pb.make_raster(in_ds, out_fn, out_data, gdal.GDT_Float32, -99)
del in_ds

5.scipy具有傅里叶变换、插值、图像处理等功能,可用于焦点分析

 

#使用scipy平滑滤波器
import os
import scipy.ndimage
from osgeo import gdal
import ospybook as pb

in_fn=''
out_fn=''

in_ds = gdal.Open(in_fn)
in_data = in_ds.GetRasterBand(1).ReadAsArray()

out_data = scipy.ndimage.filters.uniform_filter(
    in_data, size=3, mode='nearest')
#size=3代表移动窗口大小,最邻近像素填充边缘
pb.make_raster(in_ds, out_fn, out_data, gdal.GDT_Int32)
del in_ds

#利用scipy计算斜率
import os
import numpy as np
import scipy.ndimage
from osgeo import gdal
import ospybook as pb

in_fn=''
out_fn=''

#定义坡度计算函数
def slope(data, cell_width, cell_height):
    rise = ((data[6]+2*data[7]+data[8])-data[0]+2*data[1]+data[2])/(8*cell_height)
    run = ...
    dist = np.sqrt(np.square(rise)+np.square(run))
    return np.arctan(dist)*180/np.pi

in_ds = gdal.Open(in_fn)
in_band = in_ds.GetRasterBand(1)
in_data = in_band.ReadAsArray().astype(np.float32)  #float

cell_width = in_ds.GetGeoTransform()[1]
cell_height = in_ds.GetGeoTransform()[5]
out_data = scipy.ndimage.filters.generic_filter(
    in_data, slope, size=3, mode='nearest',
    extra_arguments=(cell_width, cell_height)
)
#scipy自定义滤波器
pb.make_raster(in_ds, out_fn, out_data, gdal.GDTFloat32)
del in_ds

 

6.打破焦点分析

#打破焦点分析
# (没有足够的内存存放图像的情况下,把图像分成重叠的块)

#分块的焦点分析
import os
import numpy as np
from osgeo import gdal
import ospybook as pb

in_fn=''
out_fn=''

in_ds = gdal.Open('')
in_band = in_ds.GetRasterBand(1)
xsize = in_band.SXize
ysize = in_band.YSize

driver = gdal.GetDriverByName('GTiff')
out_ds = gdal.Create(out_fn, xsize, ysize,1, gdal.GDT_Int32)
out_ds.SetProjection(in_ds.GetProjection())
out_ds.SetGeoTransform(in_ds.GetGeoTransform())
out_band = out_ds.GetRasterBand(1)
out_band.SetNoDataValue(-99)

n = 100
for i in range(0, yszie, n):
    if i+n+1 < ysize:
        rows = n + 2  #额外读取两行
    else:
        rows = ysize - i  #如果到顶就取余数
    yoff = max(0, i-1) #在0行开始读取数据
    in_data = in_band.ReadAsArray(0, yoff, xsize, rows)  #每次从(0,yoff)开始读取,rows行数据
    slices = pb.make_slices(in_data, (3,3))
    stacked_data = np.ma.stack(slices)
    out_data = np.ones(in_data.shape, np.int32)*-99
    out_data[1:-1,1:-1] = np.mean(stacked_data, 2)

    if yoff==0:  #第一次读取
        out_band.WriteArray(out_data)
    else:  #否则从第二块开始写入
        out_band.WriteArray(out_data[1:], 0, yoff+1)
        #继续从(0,yoff+1)开始写入数据
        #out_data[1:]中1是第一行向后数据,这样不要覆盖上次的最后一行数据。即out_data[1:,:
        # ]
out_band.FlushCache()
out_band.ComputeStatistics(False)
del out_ds, in_ds

7. 区域分析(histogram2d)

#区域分析
import numpy as np
import scipy.stats
from osgeo import gdal

landcover_fn = r'E:\桌面文件保存路径\gdal\osgeopy-data\osgeopy-data\Utah\landcover60.tif'
ecoregion_fn = r'E:\桌面文件保存路径\gdal\osgeopy-data\osgeopy-data\Utah\utah_ecoIII60.tif'
out_fn ='histogram.csv'

def get_bins(data):
    bins = np.unique(data)  #保留唯一的数据
    return np.append(bins, max(bins)+1)  #新增最大值+1

#直方图的行(区域)对应传入的第一个数组、直方图的列(土地覆盖)对应传入的第二个数组
hist, zone_bins, landcover_bins = np.histogram2d(
    zones.flatten(), landcover.flatten(),
    [get_bins(zones), get_bins(landcover)]
)

lc_ds = gdal.Open(landcover_fn)
lc_band = lc_ds.GetRasterBand(1)
lc_data = lc_band.ReadAsArray().flatten()
bins = np.unique(lc_data)
print(bins)
print(np.append(bins[~np.isnan(bins)], max(bins)+1))

8.利用scipy做区域分析(scipy.stats.binned_statistic_2d)

# #利用scipy做区域分析
import numpy as np
import scipy.stats
from osgeo import gdal

def get_bins(data):
    bins = np.unique(data)
    return np.append(bins, max(bins)+1)

landcover_fn =r'E:\桌面文件保存路径\gdal\osgeopy-data\osgeopy-data\Utah\landcover60.tif'
ecoregion_fn = r'E:\桌面文件保存路径\gdal\osgeopy-data\osgeopy-data\Utah\utah_ecoIII60.tif'
out_fn ='histogram.csv'

eco_ds = gdal.Open(ecoregion_fn)
eco_band = eco_ds.GetRasterBand(1)
eco_data = eco_band.ReadAsArray().flatten()
eco_bins = get_bins(eco_data)  #获取组距

lc_ds = gdal.Open(landcover_fn)
lc_band = lc_ds.GetRasterBand(1)
lc_data = lc_band.ReadAsArray().flatten()
lc_bins = get_bins(lc_data)

#输入两个数据集、用于统计的第三个数组,count计数并指定组距。输出直方图、组距和额外的输出(指示数据落入哪个组距)
#行为第一个数组,列为第二个数组,在此基础上统计第三个数组。
#e.g.若传递高程数据、mean作为第三个和第四个参数,可以
#计算每个生态区和土地覆盖区的平均高程。
hist, eco_bins2, lc_bins2, bn = \
    scipy.stats.binned_statistic_2d(
        eco_data, lc_data, lc_data, 'count',
        [eco_bins, lc_bins]
    )
# print(hist)
print(eco_bins2)  #
print(lc_bins2)   #
print(max(bn))  #落入哪个组距
hist = np.insert(hist, 0, lc_bins[:-1], 0) #把土地覆盖数据插入hist第一行
row_labels = np.insert(eco_bins[:-1], 0, 0) #0插入eco_bins第一个位置
hist = np.insert(hist, 0, row_labels, 1) #把生态数据插入hist第一列
np.savetxt(out_fn, hist, fmt='%1.0f', delimiter=',')
#1代表至少打印一个数字,.0意味小数点后没有数字,F意味浮点数

#如果想知道每个生态区最常见的土地覆盖类型,不需要知道数量
def my_mode(data):
    return scipy.stats.mode(data)[0] #返回数组中最常出现的成员和个数

mode, bins, bn = scipy.stats.binned_statistic(eco_data, lc_data, my_mode, eco_bins)
#把一个生态区进行组距,从而统计土地覆盖
print(mode)  #直方图
print(bins)  #eco数组组距
print(bn)    #落入位置

9. 全局分析(用到了gdal.RasterizeLayer和gdal.ComputeProximity

#全局分析
#邻近分析
import os
import sys
from osgeo import gdal, ogr

folder = ''  #shp数据文件夹
road_ln = '' #道路图层lyr
wilderness_ln = '' #荒地图层lyr
road_raster_fn = '' #道路栅格数据
proximity_fn = 'proximity.tif'  #邻近度栅格
cell_size = 10

shp_ds = ogr.Open(folder)
wild_lyr = shp_ds.GetLayerByName(wilderness_ln)#获取荒地图层
wild_lyr.SetAttributeFilter('WILD_NM ='Frank Church -RONR'')#属性查询
envelopes = [row.geometry().GetEnvelope() for row in wild_lyr] #图层里每个要素,获取几何体,获取最小外接四边形
coords = list(zip(*envelopes))  #zip迭代器取出四个点,划定荒地范围
minx, maxx = min(coords[0]), max(coords[1])
miny, maxy = min(coords[2]), max(coords[3])

road_lyr = shp_ds.GetLayerByName(roads_ln)  #道路图层
road_lyr.SetSpatialFilterRect(minx, miny, maxx, maxy) #在荒地范围空间查询

os.chdir(folder) 
tif_driver = gdal.GetDriverByName('GTiff')
cols = int((maxx-minx)/cellsize)  #计算区域行列数
rows = int((maxy-miny)/cellsize)

road_ds = tif_driver.Create(road_raster_fn, cols, rows)  #创建道路栅格数据
road_ds.SetProjection(road_lyr.GetSpatialRef().ExportToWkt()) #设置投影。lyr.GetSpatialRef()返回空间参考对象,需要转换成wkt|数据集才能ds.GetProjection()
road_ds.SetGeoTransform(minx, cellsize, 0, maxy, 0, -cellsize) #设置栅格数据GeoTransform地理变换

gdal.RasterizeLayer(road_ds, [1], road_lyr, burn_values=[1], callback=gdal.TermProgress)
#栅格化道路图层,道路1表示,其他0。第一个[1]是第一个波段索引;第二个1是将有要素的地方转换成栅格值的列表
prox_ds = tif_driver.Create(proximity_fn, cols, rows, 1, gdal.GDT_Int32) #创建邻近度栅格
prox_ds.SetProjection(road_ds.GetProjection())
prox_ds.SetGeoTransform(road_ds.GetGeoTransform())
gdal.ComputeProximity(   #计算proximity,结果存于proximity
    road_ds.GetRasterBand(1), prox_ds.GetRasterBand(1),
    ['DISTUNITS=GEO'], gdal.TermProgress)  #DISTUNITS指定距离单位,默认为像素,这里设为地理坐标单位

wild_ds = gdal.GetDriverByName('MEM').Create('tmp', cols, rows)  #只需要荒野区域内统计信息,所以用MEM驱动把数据存在内存
wild_ds.SetProjection(prox_ds.GetProjection())
wild_ds.SetGeoTransform(prox_ds.GetGeoTransform())
gdal.RasterizeLayer(wild_ds, [1], wild_lyr, burn_values=[1], callback=gdal.TermProgress)
#栅格化荒野图层

wild_data = wild_ds.ReadAsArray()
prox_data = prox_ds.ReadAsArray()
prox_data[wild_data==0]=-99  #非荒野区域-99
prox_ds.GetRasterBand(1).WriteArray(prox_data)
prox_ds.GetRasterBand(1).SetNoDataValue(-99) #设置非荒野区域nodata
prox_ds.FlushCache()

stats = prox_ds.GetRasterBand(1).ComputeStatistics(False, gdal.TermProgress) #计算精确值
print('Mean distance from roads is', stats[2])

del prox_ds, road_ds, shp_ds

10. 重采样制作分步切片以及获取新像素偏移的坐标

#新的重采样方法
data = np.reshape(np.arange(24), (4, 6))
data[::2,::2]  #设置步长重采样,这里像素大小增大
np.repeat(data, 2, 1) #增加数组大小(减小像素大小)重采样。在列上重复2次
np.repeat(np.repeat(data, 2, 0), 2, 1) #先在行上重复2次,再列重复2次

#如果要对原始大小四倍像素重采样,取四个像素平均值
#与移动窗口切片不同,这些切片比原始数据小很多,且他们大小与输出数组相同

#制作分步切片。前面为连续数据切片,这里设置步长,是分布切片
def make_resample_slices(data, win_size):
    row = int(data.shape[0]/win_size[0])*win_size[0]  #由于可能无法整除,计算新行
    col = int(data.shape[1]/win_size[1])*win_size[1]
    slices = []
    for i in range(win_size[0]):
        for j in range(win_size[1]):
            slices.append(data[i:row:win_size[0], j:col:win_size[1]])
    return slices        
#当新像素大小是原始像素小数倍时,这种技术会导致像素中心偏移。 #根据旧像素获取新像素偏移,得到的是偏移后的行列号坐标,即图像坐标 def get_indices(source_ds, target_width, target_height): source_geotransform = source_ds.GetGeoTransform() source_width = source_geotransform[1] #像素行分辨率 source_height = source_geotransform[5] dx = target_width/source_width #扩大倍数 dy = target_height/source_height target_x = np.arange(dx/2, source_ds.RasterXSize, dx) target_y = np.arange(dy/2, source_ds.RasterYSize, dy) return np.meshgrid(target_x, target_y) ds = gdal.Open(fn) data = ds.ReadAsArray() x, y = get_indices(ds, 25, -25) new_data = data[y.astype(int), x.astype(int)] #索引转换整数,最邻近采样

11. 双线性插值以及重采样

#还有双线性插值、三次卷积插值方法
#双线性插值,在找到新坐标后,找到最邻近的四个原始像素,与距离加权得到新的值
def bilinear(in_data, x, y):
    x -= 0.5  #索引减去0.5到输入像素中心(ds/2必定包含0.5)
    y -= 0.5
    x0 = np.floor(x).astype(int)  #取整
    x1 = x0 + 1  #相邻坐标,获取围绕目标像素的四个像素
    y0 = np.floor(y).astype(int)
    y1 = y0 + 1

    ul = in_data[y0, x0]*(y1-y)*(x1-x)  #乘两个方向上该像素到目标像素的距离
    ur = in_data[y0, x1]*(y1-y)*(x-x0)
    ll = in_data[y1, x0]*(y-y0)*(x1-x)
    lr = in_data[y1, x1]*(y-y0)*(x-x0) 

    return ul+ur+ll+lr  #加权和即像素值

#双线性插值重采样
in_fn = ''
out_fn = ''
cell_size = (0.02, -0.02)  #新像素大小
in_ds = gdal.Open(in_fn)
x, y = get_indices(in_ds, *cell_size)  #偏移的新像素x, y
outdata = bilinear(in_ds.ReadAsArray(), x, y)  #重采样

driver = gdal.GetDriverByName('GTiff')
rows, cols = outdata.shape  #新行列数
out_ds = driver.Create(out_fn, cols, rows, 1, gdal.GDT_Int32)
out_ds.SetProjection(in_ds.GetProjection())

gt = list(in_ds.GetGeoTransform())  #列表化元组
gt[1] = cell_size[0]  #更改geotransform像素分辨率
gt[5] = cell_size[1]
out_ds.SetGeoTransform(gt)

out_band = out_ds.GetRasterBand(1)
out_band.WriteArray(outdata) #读入重采样的数据
out_band.FlushCache()
out_band.ComputeStatistics(False)

#此外scipy.ndimage还有其他插值方法可供使用

12. GDAL warp及python调用

#GDAL命令重采样
gdalwarp -tr 0.02 0.02 -r bilinear first.tif final.tif
#python调用命令
import subprocess
result = subprocess.call(gdalwarp -tr 0.02 0.02 -r bilinear first.tif final.tif)

 

总结:

1. 局部分析: 计算像素到像素的基础工作,NDVI
2. 焦点分析: 使用环绕像素计算输出值的移动窗口,如斜率
3. 区域分析: 处理同一区域的像素
4. 全局分析: 邻近度分析等,涉及整个数据集

 

posted @ 2020-04-06 19:25  Rser_ljw  阅读(1140)  评论(0编辑  收藏  举报