• 使用GDAL实现DEM的地貌晕渲图(二)


    1. 问题

    之前我在《使用GDAL实现DEM的地貌晕渲图(一)》这篇文章里面讲述了DEM晕渲图的生成原理与实现,大体上来讲是通过计算DEM格网点的法向量与日照方向的的夹角,来确定该格网点的晕渲强度值。但其实关于这一点我不是很理解,这样做随着坡面与光源方向的夹角不同,确实产生了不同色调明暗效果;但晕渲图同时又有“阴坡面越陡越暗,阳坡面越陡越亮”的特性的,而阴阳坡面的划分又是跟坡度和坡向相关,之前的生成方法能体现出这种特性吗?

    经过查阅资料,却在ArcGIS的帮助文档《山体阴影工具的工作原理》(在线版本可查看这篇文章《ArcGIS教程:山体阴影工作原理》)中查阅到了晕渲图的另外一种生成算法。利用直接利用坡度和坡向的关系,算出每个点的山体阴影值:


    并且,在该文档中,还附带了一个具体的计算示例:


    具体的算法过程说的很清楚了,可惜的就是不太明白具体的原理是什么,在帮助中指向了一本1998的英文文献[Burrough, P. A. and McDonell, R. A., 1998. Principles of Geographical Information Systems (Oxford University Press, New York), 190 pp]也实在是没法深入查阅深究。而在查阅中文论文的时候,关于这一段的描述也是互相抄袭,摘录如下:

    这一段的论述反正我是没看明白的,也就不多做论述了,希望看懂这个算法的大神能指点我一下。

    2. 实现

    虽然更深入的原理没弄明白,不过作为应用者却足够能够实现其算法了。我这里通过GDAL实现了晕渲图的生成:

    #include <iostream>
    #include <algorithm>
    #include <gdal_priv.h>
    #include <osg/Vec3d>
    #include <fstream>
    
    using namespace std;
    using namespace osg;
    
    // a b c
    // d e f
    // g h i
    double CalHillshade(float *tmpBuf, double Zenith_rad, double Azimuth_rad, double dx, double dy, double z_factor)
    {
    	double dzdx = ((tmpBuf[2] + 2 * tmpBuf[5] + tmpBuf[8]) - (tmpBuf[0] + 2 * tmpBuf[3] + tmpBuf[6])) / (8 * dx);
    	double dzdy = ((tmpBuf[6] + 2 * tmpBuf[7] + tmpBuf[8]) - (tmpBuf[0] + 2 * tmpBuf[1] + tmpBuf[2])) / (8 * dy);
    
    	double Slope_rad = atan(z_factor * sqrt(dzdx*dzdx + dzdy*dzdy));
    	double Aspect_rad = 0;
    	if (abs(dzdx) > 1e-9)
    	{
    		Aspect_rad = atan2(dzdy, -dzdx);
    		if (Aspect_rad < 0)
    		{
    			Aspect_rad = 2 * PI + Aspect_rad;
    		}
    	}
    	else
    	{
    		if (dzdy > 0)
    		{
    			Aspect_rad = PI / 2;
    		}
    		else if (dzdy < 0)
    		{
    			Aspect_rad = 2 * PI - PI / 2;
    		}
    		else
    		{
    			Aspect_rad = Aspect_rad;
    		}
    	}
    
    	double Hillshade = 255.0 * ((cos(Zenith_rad) * cos(Slope_rad)) + (sin(Zenith_rad) * sin(Slope_rad) * cos(Azimuth_rad - Aspect_rad)));
    	return Hillshade;
    }
    
    
    int main()
    {
    	GDALAllRegister();          //GDAL所有操作都需要先注册格式
    	CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");  //支持中文路径
    
    	const char* demPath = "D:/CloudSpace/我的技术文章/素材/DEM的渲染/dst.tif";
    	//const char* demPath = "D:/Data/imgDemo/K51E001022/k51e001022dem/w001001.adf";
    	
    	GDALDataset* img = (GDALDataset *)GDALOpen(demPath, GA_ReadOnly);
    	if (!img)
    	{
    		cout << "Can't Open Image!" << endl;
    		return 1;
    	}
    
    	int imgWidth = img->GetRasterXSize();   //图像宽度
    	int imgHeight = img->GetRasterYSize();  //图像高度
    	int bandNum = img->GetRasterCount();    //波段数
    	int depth = GDALGetDataTypeSize(img->GetRasterBand(1)->GetRasterDataType()) / 8;    //图像深度
    
    	GDALDriver *pDriver = GetGDALDriverManager()->GetDriverByName("GTIFF"); //图像驱动
    	char** ppszOptions = NULL;
    	ppszOptions = CSLSetNameValue(ppszOptions, "BIGTIFF", "IF_NEEDED"); //配置图像信息
    	const char* dstPath = "D:\dst.tif";
    	int bufWidth = imgWidth;
    	int bufHeight = imgHeight;
    	int dstBand = 1;
    	int dstDepth = 1;
    	GDALDataset* dst = pDriver->Create(dstPath, bufWidth, bufHeight, dstBand, GDT_Byte, ppszOptions);
    	if (!dst)
    	{
    		printf("Can't Write Image!");
    		return false;
    	}
    
    	dst->SetProjection(img->GetProjectionRef());
    	double padfTransform[6] = { 0 };
    	if (CE_None == img->GetGeoTransform(padfTransform))
    	{
    		dst->SetGeoTransform(padfTransform);
    	}
    
    	//申请buf
            depth = 4;
    	size_t imgBufNum = (size_t)bufWidth * bufHeight * bandNum;
    	float *imgBuf = new float[imgBufNum];
    	//读取
    	img->RasterIO(GF_Read, 0, 0, bufWidth, bufHeight, imgBuf, bufWidth, bufHeight,
    		GDT_Float32, bandNum, nullptr, bandNum*depth, bufWidth*bandNum*depth, depth);
    
    	if (bandNum != 1)
    	{
    		return 1;
    	}
    
    	//
    	double startX = padfTransform[0];			//左上角点坐标X
    	double dx = padfTransform[1];			//X方向的分辨率
    	double startY = padfTransform[3]; 			//左上角点坐标Y
    	double dy = padfTransform[5];			//Y方向的分辨率
    		
    	//申请buf
    	size_t dstBufNum = (size_t)bufWidth * bufHeight * dstBand * dstDepth;
    	GByte *dstBuf = new GByte[dstBufNum];
    	memset(dstBuf, 0, dstBufNum*sizeof(GByte));
    
    	//设置方向:平行光
    	double solarAltitude = 45.0;
    	double solarAzimuth = 315.0;
    	
    	//
    	double Zenith_rad = osg::DegreesToRadians(90 - solarAltitude);
    	double Azimuth_math = 360.0 - solarAzimuth + 90;
    	if (Azimuth_math >= 360.0)
    	{
    		Azimuth_math = Azimuth_math - 360.0;
    	}	
    	double Azimuth_rad = osg::DegreesToRadians(Azimuth_math);
    
    	//a b c
    	//d e f
    	//g h i
    	double z_factor = 1;
    	for (int yi = 1; yi < bufHeight-1; yi++)
    	{
    		for (int xi = 1; xi < bufWidth-1; xi++)
    		{
    			size_t e = (size_t)bufWidth * yi + xi;
    			size_t f = e + 1;
    			size_t d = e - 1;
    
    			size_t b = e - bufWidth;
    			size_t c = b + 1;
    			size_t a = b - 1;
    
    			size_t h = e + bufWidth;
    			size_t i = h + 1;
    			size_t g = h - 1;
    			
    			float tmpBuf[9] = { imgBuf[a], imgBuf[b], imgBuf[c], imgBuf[d], imgBuf[e], imgBuf[f], imgBuf[g],imgBuf[h], imgBuf[i] };
    			double Hillshade = CalHillshade(tmpBuf, Zenith_rad, Azimuth_rad, dx, -dy, z_factor);
    	
    			dstBuf[e] = (GByte)(std::min(std::max(Hillshade, 0.0),255.0));
    		}
    	}
    
    	//写入
    	dst->RasterIO(GF_Write, 0, 0, bufWidth, bufHeight, dstBuf, bufWidth, bufHeight,
    		GDT_Byte, dstBand, nullptr, dstBand*dstDepth, bufWidth*dstBand*dstDepth, dstDepth);
    	
    	//释放
    	delete[] imgBuf;
    	imgBuf = nullptr;
    
    	//释放
    	delete[] dstBuf;
    	dstBuf = nullptr;
    
    	//
    	GDALClose(dst);
    	dst = nullptr;
    
    	GDALClose(img);
    	img = nullptr;
    
    	return 0;
    }
    

    最终得到的晕渲结果和ArcMap的晕渲结果比较,几乎是一模一样的:

    后续会正式在这个基础之上实现彩色的晕渲图。

    3. 参考

    [1]. ArcGIS帮助:山体阴影工具的工作原理。
    [2]. 基于视觉表象的彩色晕渲地图色彩设计.郭礼珍等.2004

  • 相关阅读:
    ExquisiteWnd
    GDIPlusEncapsulation
    COleDateTimeSpan
    Create Win32 Window
    vagrant 安装配置,PhpStorm10 设置远程调试
    PHPExcel导出主要代码记录
    常用js方法集合,动态加载js方法--判断变量是否为空--截取小数点后几位--截取带中文的字条串
    最近遇到的各种坑
    控制台运行bee run project 报[ERRO] Fail to watch directory[too many open files]
    Mac 安装beego And bee工具
  • 原文地址:https://www.cnblogs.com/charlee44/p/11219168.html
Copyright © 2020-2023  润新知