不只是图片:深入理解 GIS 栅格数据本质与 GDAL 读写实战

GIS 栅格数据只是一张普通图片吗?其实它可以是高程、降雨量、土地类型,甚至是二维空间信号。本文节选自新书《GIS基础原理与技术实践》第5章,带你穿透表象,掌握栅格数据的本质与 GDAL 开发核心技能。

GIS基础原理与技术实践

第5章 地理空间数据之栅格

导言

在上一章节中,我们对地理空间数据进行了初步的介绍,并详细的论述了其表现形式之一的矢量数据。而在本章中,我们会详细论述地理空间数据的另外一种表现形式,也就是栅格数据。矢量和栅格是GIS地理空间数据最基础的两种数据类型,是一定需要理解和掌握的。

5.1 栅格数据的深入认识

通过第4.1节中的介绍,我们对栅格数据有了一个初步的认识:栅格是地理空间连续实体的表达。接下来在本节中,我们会进一步探索栅格的技术细节,加深对栅格数据的理解。

5.1.1 离散还是连续

记得笔者有一次跟同事争论,图像数据(也就是栅格数据)到底是离散的还是连续的?当时笔者还是个GIS菜鸟,认为图像是离散的。从前面介绍的知识来说,很显然栅格是地理的连续表达。与矢量只能知道具体的要素的坐标不同,我们可以很容易获取每一个栅格单元具体的地理坐标,体现了数据的连续性。然而,当我们把一张图像放大,我们很容易会看到一个个具体的栅格单元,如下图5.1所示:

图5.1 放大后的栅格单元

这些栅格单元中突变的颜色表明,栅格数据并不是我们想象的那样单纯的具有连续性。栅格数据对地理空间的表达总是会受制于采样频率,最直观的体现就是分辨率。如同现实中显示器一样,分辨率越高意味着像素的数目越多,显示效果就越精细。但是无论多精细,其实都是由离散的栅格单元在二维平面上连续铺盖成的格网组成的。如此说来,笔者在菜鸟时期认为栅格数据是离散的,可以说还是具有一定的正确性的,尽管是误打误撞。离散还是连续,并不是绝对的,关键还在于是如何看待和认知:

  • 在宏观意义上,栅格数据毫无疑问是连续的,这是区别与矢量数据的根本特性。只要是在栅格数据的范围之内,就可以获取任意位置的地理对象值。
  • 在微观意义上,栅格数据也可以认为是离散的。受制于数据表达的载体,总会在一个无法细分的尺度上,用一个离散的栅格单元来代表一定连续范围内的地理对象值。

栅格数据关于离散还是连续的讨论,我们在后面还会涉及到。

5.1.2 栅格的表现形式

我们在4.1节中介绍过,栅格数据的一种具体表现形式就是我们经常见到的图片(有时称为图像,或者影像)。但是认为图片就是栅格,这种理解并不全面,栅格数据的内涵要丰富得多。

我们最常见的图片是24位真彩色图片,因为每个像素都由红绿蓝(RGB)3种颜色值混合而成,而每个波段的颜色值都由而一个8位整型表示。这样,真彩色图片能表示的颜色就有 256 X 256 X 256 = 16777216 种不同颜色,这恰好大于人类能识别1000万种颜色的限制。另外,出于数据叠加显示等目的,还会将第4个波段设置成透明度。其实透明度并不是一个颜色值,其是当前颜色值与背景颜色值的混合。

图5.2 国家天地图上的真彩色数字影像图

在遥感领域,很多传感器并不是只能接受红绿蓝这三种可见光的波段,一些不可见的波段也可以接收到(比如红外波段),这主要取决于传感器的波长分辨率。因此,第4个波段也不一定就是透明度,而是其光谱的反射值或者辐射值。甚至于可以存在4个以上的波段。光谱的反射值也不一定就是8位整型,用于遥感探测的传感器要敏感的多,因此每个波段的光谱反射值是16位、32位甚至64位数值都是有可能的。

图5.3 遥感影像中的不可见光波段

栅格数据中格网单元的取值可以是可见光的颜色,也可以是遥感获取的具体探测值。那么,存放其他类型的数据是可以的吗?当然也是可以的,比如我们可以在栅格数据中储存高程值,就可以表达地形,成为栅格形式的DEM(Digital Elevation Model,数字高程模型)数据。这一点我们会在后续的地形章节中专门论述它。

图5.4 SRTM(Shuttle Radar Topography Mission,航天飞机雷达地形测量)地形数据晕渲图

既然栅格单元可以存放高程,那么存放一些诸如表达降雨量,污染浓度这样的测量值,或者土地类型这样的分类值也是可以的——此类数据就是与业务紧密关联的专题栅格数据,具有很大的实用价值,可以在其基础之上进行专题分析,例如利用降雨量专题数据进行淹没分析。当然,专题栅格数据如果需要进行可视化的话,就需要进行转换。

图5.5 国家天地图上的人口密度专题图

栅格数据(图像)还可以看成是信号。在常规的认知中,我们通常认为信号是随时间t变化的一种东西,比如电磁波或者声波。实际上,栅格数据(图像)可以认为是一个沿着空间分布、随着X空间轴和Y空间轴变化的二维信号。有这一点认知非常重要,因为很多栅格数据处理的算法都需要借助于信号的理论,比如我们后续会介绍的滤波。

最后,在数学中可以认为栅格数据就是一个很大的矩阵,可以进行复杂的矩阵运算。很多运算数据都可以转换成矩阵,从而被转换成图片格式来保存,这也与我们认知的常规图片不同。

5.1.3 正射还是透视

以GIS中最常用的地图来说,我们至少有这样一个认识:地图上每一段相同距离所代表的实际距离都相等。如果不具备这样的特性,地图就不可能辅助我们进行导航等空间位置上的决策。在GIS中,大部分栅格数据就是这种具备相同比例尺的光学影像数据,它好像是很多道平行的光线同时反射到摄像机成像生成的,所以通常被称为数字正射影像图(DOM,Digital Orthophoto Map)。

但是在现实中,理想的正射成像模型几乎不存在,大多数摄像机都是透视成像模型,其生成的图像最大的特点就是近大远小,并且投影的光线会聚于一点。这也是真实的人眼成像的特点。其实,数字正射影像图就是通过透视成像模型的图片做几何纠正生成的,这一点在GIS的关联学科《摄影测量学》中有详细的介绍。如下图5.6所示为正射成像模型和透视成像模型示意图:

图5.6 正射成像模型和透视成像模型

正射还是透视只是图像的生成的两种不同的方式,并不会影响图像或者栅格数据本身的性质。在GIS中的栅格数据大多数指的是数字正射影像图,能够使用专业的GIS方法进行处理,这是常规的基于透视成像模型的图片所不具备的。

5.2 栅格数据开发基础

5.2.1 栅格数据读取

与矢量数据一样,可以使用第三方开源库GDAL来实现栅格数据的基础开发。GDAL提供了市面上绝大多数常见栅格数据的支持,并将其抽象成一个数据集对象。读取栅格数据的示例如下例5.1所示:

// 例5.1 使用GDAL读取栅格数据
#include <gdal_priv.h>

#include <iostream>

using namespace std;

int main() {
  GDALAllRegister();

  string srcFile = getenv("GISBasic");
  srcFile = srcFile + "/../Data/Raster/berry_ali_2011127_crop_geo.tif";

  GDALDataset* img = (GDALDataset*)GDALOpen(srcFile.c_str(), GA_ReadOnly);
  if (!img) {
    return 1;
  }

  int imgWidth = img->GetRasterXSize();   //图像宽度
  int imgHeight = img->GetRasterYSize();  //图像高度
  int bandNum = img->GetRasterCount();    //波段数
  int depth = GDALGetDataTypeSize(img->GetRasterBand(1)->GetRasterDataType()) / 8;  //图像深度

  cout << "宽度:" << imgWidth << '\n';
  cout << "高度:" << imgHeight << '\n';
  cout << "波段数:" << bandNum << '\n';
  cout << "深度:" << depth << '\n';

  GDALClose(img);
  img = nullptr;
}

在这个示例中,从栅格数据文件中创建了一个数据集对象,并且获取了影像的宽度、高度和波段数(通道数)和深度。深度有点难以理解,指的是栅格存储数据类型的字节数。常规的影像数据都采用8位无符号整型进行存储,因此深度都为1。
本例运行结果如下:

宽度:4000
高度:7200
波段数:4
深度:1

5.2.2 栅格数据创建

另一方面,也可以根据一个数据集对象创建栅格数据文件,也就是栅格数据的写出操作。与第4.4.2节中创建矢量的步骤类似,同样通过GDAL的数据驱动GDALDriver,传入数据类型的名称来创建对应格式的栅格文件,如下例5.2所示,创建了一个tif格式的栅格数据:

// 例5.2 使用GDAL创建栅格数据
#include <gdal_priv.h>

#include <iostream>

using namespace std;

int main() {
  GDALAllRegister();  //注册格式

  string dstFile = getenv("GISBasic");
  dstFile = dstFile + "/../Data/Raster/dst.tif";

  GDALDriver* pDriver =
      GetGDALDriverManager()->GetDriverByName("GTiff");  //图像驱动
  char** ppszOptions = NULL;
  ppszOptions =
      CSLSetNameValue(ppszOptions, "BIGTIFF", "IF_NEEDED");  //配置图像信息

  int imgWidth = 256;
  int imgHeight = 256;
  int bandNum = 3;
  GDALDataset* dst = pDriver->Create(dstFile.c_str(), imgWidth, imgHeight,
                                     bandNum, GDT_Byte, ppszOptions);
  if (!dst) {
    printf("Can't Write Image!");
    return 1;
  }

  GDALClose(dst); 
  return 0;
}

上例中我们创建了一个宽256,高256,波段数3,byte类型(8位,深度1)的栅格数据文件:

GDALDriver* pDriver =
      GetGDALDriverManager()->GetDriverByName("GTiff");  //图像驱动
  //...
  GDALDataset* dst = pDriver->Create(dstFile.c_str(), imgWidth, imgHeight,
                                     bandNum, GDT_Byte, ppszOptions);

数据类型的名称“GTIFF”是GDAL定义的驱动名,表示创建的数据集对象是一个tif格式的栅格数据文件。一些常用栅格数据格式在GDAL中数据驱动如下表5.1所示:

名称 全称
JPEG JPEG JFIF File Format
PNG Portable Network Graphics
BMP Microsoft Windows Device Independent Bitmap
GTiff GeoTIFF File Format
HFA Erdas Imagine .img
ENVI ENVI .hdr Labelled Raster

其中,JPEG、PNG和BMP就是我们经常见到的图像数据格式;GTiff(GeoTIFF)、HFA(Erdas Imagine .img)和ENVI(ENVI .hdr Labelled Raster)则是专业的GIS栅格数据。前者通用性更强,能被常规的图片软件所识别;而后者专业性更高,一些栅格数据的特性常规图像数据无法支持(例如存在数据量大小限制),因此只能使用专业GIS栅格数据,一般情况仅被GIS相关的软件支持。

另一点值得注意的是,在创建特定数据集对象时,可以根据特定格式的需求进行配置:

char** ppszOptions = NULL;
  ppszOptions =
      CSLSetNameValue(ppszOptions, "BIGTIFF", "IF_NEEDED");  //配置图像信息
  //...
  GDALDataset* dst = pDriver->Create(dstFile.c_str(), imgWidth, imgHeight,
                                     bandNum, GDT_Byte, ppszOptions);

这里配置参数的意思是,在需要的时候使用“BIGTIFF”格式而不是“TIFF”,以避免当栅格数据文件的大小超过4G的时候会创建失败。

最后,在创建数据集完成之后,不要忘了关闭数据集:

GDALClose(dst);

此时创建的数据集文件就是一张黑色的栅格图片(因为关闭后默认填充0像素值),如下图5.7所示:

图5.7 创建的栅格数据集文件


本文节选自作者新书《GIS基础原理与技术实践》第5章。书中系统讲解 GIS 核心理论与多语言实战,适合开发者与高校师生。

📚 配套资源开源GitHub | GitCode

🛒 支持正版京东当当

posted @ 2026-01-20 13:37  charlee44  阅读(54)  评论(0)    收藏  举报