我想知道是否有办法找到具有特定值的栅格的最小边界范围。我从全局图像中裁剪了一个栅格,并将范围设置为具有大量NoData区域的全局范围。我想从此栅格中删除NoData区域,并仅保留包含特定值像素的区域。我怎样才能做到这一点?
这是我的示例:我想提取value = 1(蓝色区域)并使用蓝色区域的范围而不是整个世界进行进一步处理。
我想知道是否有办法找到具有特定值的栅格的最小边界范围。我从全局图像中裁剪了一个栅格,并将范围设置为具有大量NoData区域的全局范围。我想从此栅格中删除NoData区域,并仅保留包含特定值像素的区域。我怎样才能做到这一点?
这是我的示例:我想提取value = 1(蓝色区域)并使用蓝色区域的范围而不是整个世界进行进一步处理。
Answers:
如果我已经正确理解了这个问题,这听起来像是您想知道不为null的值的最小边界框。也许您可以将栅格转换为多边形,选择感兴趣的多边形,然后将其转换回栅格。然后,您可以查看应该为您提供最小边框的属性值。
诀窍是计算具有值的数据的限制。 也许最快,最自然,最通用的方法是使用区域汇总:通过将所有非NoData单元用于区域,包含X和Y坐标的网格的区域最小值和最大值将提供完整范围。
ESRI不断改变进行这些计算的方式。例如,坐标网格的内置表达式已随ArcGIS 8一起删除,并且似乎未返回。只是为了好玩,这是一组快速,简单的计算,无论如何都可以完成工作。
通过将网格与其自身等价,将网格转换为单个区域,如
“我的网格” ==“我的网格”
通过对值1的常数网格进行流累积来创建列索引网格。(索引将从0开始。)如果需要,将其乘以单元格大小并添加原点的x坐标即可获得x坐标网格 “ X”(如下所示)。
类似地,通过对值64的常数栅格进行流累积来创建行索引栅格(然后创建y坐标栅格 “ Y”)。
使用步骤(1)中的区域网格来计算“ X”和“ Y”的区域最小值和最大值:现在您具有所需的范围。
(该区域的范围,如两个区域统计表中所示,在此图中用矩形轮廓线表示。网格“ I”是在步骤(1)中获得的区域网格。)
要走得更远,您将需要从它们的输出表中提取这四个数字,并使用它们来限制分析范围。复制原始网格,并在受限的分析范围内完成任务。
这是ArcGIS 10.1+ 的@whubers方法的一个版本,作为python工具箱(.pyt)。
import arcpy
class Toolbox(object):
def __init__(self):
"""Define the toolbox (the name of the toolbox is the name of the
.pyt file)."""
self.label = "Raster Toolbox"
self.alias = ""
# List of tool classes associated with this toolbox
self.tools = [ClipNoData]
class ClipNoData(object):
def __init__(self):
"""Clip raster extent to the data that have values"""
self.label = "Clip NoData"
self.description = "Clip raster extent to the data that have values. "
self.description += "Method by Bill Huber - https://gis.stackexchange.com/a/55150/2856"
self.canRunInBackground = False
def getParameterInfo(self):
"""Define parameter definitions"""
params = []
# First parameter
params+=[arcpy.Parameter(
displayName="Input Raster",
name="in_raster",
datatype='GPRasterLayer',
parameterType="Required",
direction="Input")
]
# Second parameter
params+=[arcpy.Parameter(
displayName="Output Raster",
name="out_raster",
datatype="DERasterDataset",
parameterType="Required",
direction="Output")
]
return params
def isLicensed(self):
"""Set whether tool is licensed to execute."""
return arcpy.CheckExtension('spatial')==u'Available'
def execute(self, parameters, messages):
"""See https://gis.stackexchange.com/a/55150/2856
##Code comments paraphrased from @whubers GIS StackExchange answer
"""
try:
#Setup
arcpy.CheckOutExtension('spatial')
from arcpy.sa import *
in_raster = parameters[0].valueAsText
out_raster = parameters[1].valueAsText
dsc=arcpy.Describe(in_raster)
xmin=dsc.extent.XMin
ymin=dsc.extent.YMin
mx=dsc.meanCellWidth
my=dsc.meanCellHeight
arcpy.env.extent=in_raster
arcpy.env.cellSize=in_raster
arcpy.AddMessage(out_raster)
## 1. Convert the grid into a single zone by equating it with itself
arcpy.AddMessage(r'1. Convert the grid into a single zone by equating it with itself...')
zones = Raster(in_raster) == Raster(in_raster)
## 2. Create a column index grid by flow-accumulating a constant grid
## with value 1. (The indexes will start with 0.) Multiply this by
## the cellsize and add the x-coordinate of the origin to obtain
## an x-coordinate grid.
arcpy.AddMessage(r'Create a constant grid...')
const = CreateConstantRaster(1)
arcpy.AddMessage(r'2. Create an x-coordinate grid...')
xmap = (FlowAccumulation(const)) * mx + xmin
## 3. Similarly, create a y-coordinate grid by flow-accumulating a
## constant grid with value 64.
arcpy.AddMessage(r'3. Create a y-coordinate grid...')
ymap = (FlowAccumulation(const * 64)) * my + ymin
## 4. Use the zone grid from step (1) to compute the zonal min and
## max of "X" and "Y"
arcpy.AddMessage(r'4. Use the zone grid from step (1) to compute the zonal min and max of "X" and "Y"...')
xminmax=ZonalStatisticsAsTable(zones, "value", xmap,r"IN_MEMORY\xrange", "NODATA", "MIN_MAX")
xmin,xmax=arcpy.da.SearchCursor(r"IN_MEMORY\xrange", ["MIN","MAX"]).next()
yminmax=ZonalStatisticsAsTable(zones, "value", ymap,r"IN_MEMORY\yrange", "NODATA", "MIN_MAX")
ymin,ymax=arcpy.da.SearchCursor(r"IN_MEMORY\yrange", ["MIN","MAX"]).next()
arcpy.Delete_management(r"IN_MEMORY\xrange")
arcpy.Delete_management(r"IN_MEMORY\yrange")
# Write out the reduced raster
arcpy.env.extent = arcpy.Extent(xmin,ymin,xmax+mx,ymax+my)
output = Raster(in_raster) * 1
output.save(out_raster)
except:raise
finally:arcpy.CheckInExtension('spatial')
我设计了一个基于gdal和numpy的解决方案。它将栅格矩阵分为行和列,并删除任何空的行/列。在此实现中,“空”的值小于1,并且仅占单个波段栅格的值。
(当我写这篇文章时,我意识到这种扫描线方法仅适用于没有数据“项圈”的图像。如果您的数据是零星的岛屿,那么岛屿之间的空间也会掉落,将所有东西挤在一起,完全弄乱了地理配准)
业务部分(需要充实,不能按原样工作):
#read raster into a numpy array
data = np.array(gdal.Open(src_raster).ReadAsArray())
#scan for data
non_empty_columns = np.where(data.max(axis=0)>0)[0]
non_empty_rows = np.where(data.max(axis=1)>0)[0]
# assumes data is any value greater than zero
crop_box = (min(non_empty_rows), max(non_empty_rows),
min(non_empty_columns), max(non_empty_columns))
# retrieve source geo reference info
georef = raster.GetGeoTransform()
xmin, ymax = georef[0], georef[3]
xcell, ycell = georef[1], georef[5]
# Calculate cropped geo referencing
new_xmin = xmin + (xcell * crop_box[0]) + xcell
new_ymax = ymax + (ycell * crop_box[2]) - ycell
cropped_transform = new_xmin, xcell, 0.0, new_ymax, 0.0, ycell
# crop
new_data = data[crop_box[0]:crop_box[1]+1, crop_box[2]:crop_box[3]+1]
# write to disk
band = out_raster.GetRasterBand(1)
band.WriteArray(new_data)
band.FlushCache()
out_raster = None
在完整脚本中:
import os
import sys
import numpy as np
from osgeo import gdal
if len(sys.argv) < 2:
print '\n{} [infile] [outfile]'.format(os.path.basename(sys.argv[0]))
sys.exit(1)
src_raster = sys.argv[1]
out_raster = sys.argv[2]
def main(src_raster):
raster = gdal.Open(src_raster)
# Read georeferencing, oriented from top-left
# ref:GDAL Tutorial, Getting Dataset Information
georef = raster.GetGeoTransform()
print '\nSource raster (geo units):'
xmin, ymax = georef[0], georef[3]
xcell, ycell = georef[1], georef[5]
cols, rows = raster.RasterYSize, raster.RasterXSize
print ' Origin (top left): {:10}, {:10}'.format(xmin, ymax)
print ' Pixel size (x,-y): {:10}, {:10}'.format(xcell, ycell)
print ' Columns, rows : {:10}, {:10}'.format(cols, rows)
# Transfer to numpy and scan for data
# oriented from bottom-left
data = np.array(raster.ReadAsArray())
non_empty_columns = np.where(data.max(axis=0)>0)[0]
non_empty_rows = np.where(data.max(axis=1)>0)[0]
crop_box = (min(non_empty_rows), max(non_empty_rows),
min(non_empty_columns), max(non_empty_columns))
# Calculate cropped geo referencing
new_xmin = xmin + (xcell * crop_box[0]) + xcell
new_ymax = ymax + (ycell * crop_box[2]) - ycell
cropped_transform = new_xmin, xcell, 0.0, new_ymax, 0.0, ycell
# crop
new_data = data[crop_box[0]:crop_box[1]+1, crop_box[2]:crop_box[3]+1]
new_rows, new_cols = new_data.shape # note: inverted relative to geo units
#print cropped_transform
print '\nCrop box (pixel units):', crop_box
print ' Stripped columns : {:10}'.format(cols - new_cols)
print ' Stripped rows : {:10}'.format(rows - new_rows)
print '\nCropped raster (geo units):'
print ' Origin (top left): {:10}, {:10}'.format(new_xmin, new_ymax)
print ' Columns, rows : {:10}, {:10}'.format(new_cols, new_rows)
raster = None
return new_data, cropped_transform
def write_raster(template, array, transform, filename):
'''Create a new raster from an array.
template = raster dataset to copy projection info from
array = numpy array of a raster
transform = geo referencing (x,y origin and pixel dimensions)
filename = path to output image (will be overwritten)
'''
template = gdal.Open(template)
driver = template.GetDriver()
rows,cols = array.shape
out_raster = driver.Create(filename, cols, rows, gdal.GDT_Byte)
out_raster.SetGeoTransform(transform)
out_raster.SetProjection(template.GetProjection())
band = out_raster.GetRasterBand(1)
band.WriteArray(array)
band.FlushCache()
out_raster = None
template = None
if __name__ == '__main__':
cropped_raster, cropped_transform = main(src_raster)
write_raster(src_raster, cropped_raster, cropped_transform, out_raster)
MemoryError
Source raster (geo units): Origin (top left): 2519950.0001220703, 5520150.00012207 Pixel size (x,-y): 100.0, -100.0 Columns, rows : 42000, 43200 Traceback (most recent call last): File "D:/11202067_COACCH/local_checkout/crop_raster.py", line 72, in <module> cropped_raster, cropped_transform = main(src_raster) File "D:/11202067_COACCH/local_checkout/crop_raster.py", line 22, in main data = np.array(raster.ReadAsArray()) MemoryError
尽管具有所有分析能力,但是ArcGIS缺乏基本的栅格操作,而传统的桌面图像编辑器(如GIMP)可以找到这些基本的栅格操作。它期望您对输出栅格使用与输入栅格相同的分析范围,除非您手动覆盖“ 输出范围”环境设置。由于这正是您要查找的东西,而不是设置的东西,因此ArcGIS的处事方式正在妨碍您。
尽管我尽了最大的努力,并且没有求助于编程,但我找不到方法来获得所需图像子集的范围(没有栅格到矢量的转换,这在计算上是浪费的)。
相反,我使用GIMP使用按颜色选择工具来选择蓝色区域,然后反转选择,按Delete键删除其余像素,再次反转选择,将图像裁剪为选择,最后将其导出回PNG。GIMP将其保存为1位深度图像。结果如下:
当然,由于您的样本图像缺少空间参考成分,并且GIMP没有空间感知,因此输出图像与样本输入一样有用。您将需要对其进行地理配准,以便在空间分析中使用它。
这是使用SAGA GIS的一种可能性:http : //permalink.gmane.org/gmane.comp.gis.gdal.devel/33021
在SAGA GIS中,有一个“裁剪到数据”模块(在Grid Tools模块库中),该模块执行任务。
但这需要您使用GDAL导入模块导入Geotif,在SAGA中对其进行处理,最后再通过GDAL导出模块将其导出为Geotif。
仅使用ArcGIS GP工具的另一种可能性是使用Raster到 TIN从栅格构建TIN,使用TIN Domain计算边界,然后按边界(或使用Feature Envelope转换为Polygon的信封)修剪栅格。