GDAL,我暗恋你很久了!

初识GDAL

GDAL(Geospatial Data Abstraction Library),是一个用于矢量和栅格格式数据之间的转换器。由开源地理空间基金会在 X/MIT 风格的开源许可下发布。作为一个库,它为所有支持的格式向调用应用程序提供单个栅格抽象数据模型和单个矢量抽象数据模型。它还带有各种有用的命令行实用程序,用于数据转换和处理。该新闻页面描述了2021年10月GDAL / OGR 3.3.3版本。

GIS的基础设施-GDAL

个人认为,GDAL可以称为GIS的基础设施 ,为什么这么说呢?因为GDAL无论是在开源GIS软件还是商业GIS软件中都得到了广泛的应用。我们通过下表来看看,哪些软件都在使用GDAL。

软件名称 用途
ESRI ArcGIS 9.2+ A popular GIS platform.
ERDAS ER Viewer Image viewer for very large JPEG 2000 and ECW files.
GeoServer a open source software server written in Java that allows users to share and edit geospatial data.
Google Earth A 3D world viewer.
IDRISI A GIS and Image Processing Windows Desktop application. Uses GDAL to import/export/warp raster data.
NASA Ames Stereo Pipeline Software for creating terrain models and ortho images from planetary stereo images.
QGIS A cross platform desktop GIS.
SkylineGlobe The Skyline suite of interactive applications allows you to build, view, query and analyze customized, virtual 3D landscapes.
PostgreSQL OGR Foreign Data Wrapper Expose OGR layer as PostgreSQL foreign tables.
PostGIS spatial database extender for PostgreSQL: The raster loader and many of the raster SQL functions rely on GDAL.
PDAL Point Cloud Data Abstraction Library
GRASS GIS A raster/vector open source GIS that uses GDAL for raster/vector import and export (via r.in.gdal/r.out.gdal)
NASA WorldWind Multiplatform virtual globe library to quickly and easily create interactive visualizations of 3D globes, map and geographical information.
R A free software environment for statistical computing and graphics, with bindings to GDAL via the rgdal package.
WindNinja wind model for fire behavior modeling.

知道大家英语好,就不翻译了哦!单词都很简单~

GDAL与OGR的关系

OGR曾经是一个独立的矢量IO库,灵感来自于OpenGIS Simple Features,它是从GDAL中分离出来的。在GDAL 2.0版本中,GDAL和OGR组件被集成在一起。

OGR过去是OpenGIS简单特性参考实现的缩写。然而,由于OGR不完全符合OpenGIS简单特性规范,并且没有被批准作为该规范的参考实现,因此名称改为OGR简单特性库。这个名字中OGR的唯一含义是历史性的。在库的源代码中,OGR也是用于类名、文件名等的前缀。

搭建GDAL开发环境

1. 搭建GDAL的Windows开发环境

由于在实际开发中,我们大多都是使用Windows机器来进行工作,因此搭建windows的开发环境就显得格外重要。但是在windows平台下编译GDAL步骤较为繁琐,且存在一定的坑,对于非C/C++/Qt开发人员来说需要额外的精力去学习和摸索。如果你感兴趣的话,可以参考这篇博主的文章完成GDAL的编译

为了节省时间,并且能够多快好省的直接进入GDAL的学习环节,我们这里使用别人编译好的GDAL jar包和动态链接库文件。

点我进入下载编译好的GDAL页面

下载的页面会跳转至GISInternals页面,关于这个网站的介绍如下所示:

About GISInternals

GISInternals is an online system for creating daily built binary packages for the GDAL and MapServer projects. The anchestor of this system has been provided to be the Windows buildslaves for the osgeo buildbot in 2007. The build system in the current form (providing downloadable packages) has been set up in 2009. As of this time, the system has been continuously improved by adding more and more packages to make the life easier for the users and developers of these open source projects. During this time the number of the visitors and the amount of the downloads continues to grow, and the site has been visited from more than 160 countries around the world.

GISInternals 是一个在线系统,用于为 GDAL 和 MapServer 项目创建每日构建的二进制包。 2007年已经提供了这个系统的源头,作为osgeo buildbot的Windows buildslaves。目前形式的构建系统(提供可下载的包)已经在2009年建立起来了。截至目前,该系统一直在不断改进 通过添加越来越多的包,让这些开源项目的用户和开发人员的生活更轻松。 在此期间,访问者数量和下载量持续增长,该站点已被全球 160 多个国家/地区访问。

由此可见GISInternals网站是一个专门用于构建GDAL和MapServer工具网站。

进入下载页面选择合适的版本,我们这里选择release-1900-x64-gdal-2-3-1-mapserver-7-2-0.zip的版本,下载好之后,解压文件后文件列表如下所示:

image.png

  • 然后我们把解压后的/bin/gdal/java下的gdalalljni.dll文件复制到我们安装Java的bin目录下,

    image.png

    值得需要注意的是下载早期的编译好的版本,该目录下会是4个.dll文件,分别是gdalconsjni.all,gdaljni.dll,ogrjni.dll,osrjni.dll文件。这是因为后来的新版本把这4个.dll文件合并成了一个文件,我们从文件名字就可以看出端倪gdalalljni.dll.
    image.png

    这里无论是你下载的早期版本,比如:release-1600-x64-gdal-2-1-3-mapserver-7-0-4,还是新版本,比如:release-1916-x64-gdal-3-3-3-mapserver-7-6-4都是可以的,新版本会有更多的新功能和新接口可以去探索和使用。

  • 然后把gdal.jar包文件放到你的java工程的libs目录下,使用maven的gav坐标把这个jar包引入你的maven工程里面,如下图所示,至于这里面的vesion你可以随便填写,这个无所谓。

  • <dependency>
                <groupId>org.gdal</groupId>
                <artifactId>gdal</artifactId>
                <version>1.9.2</version>
                <scope>system</scope>
                <systemPath>${project.basedir}/libs/gdal.jar</systemPath>
    </dependency>
    

至此,windows环境下的gdal 环境搭建就完成了,稍后我们会进行实战操作···

2. 搭建GDAL 的CentOS运行环境

我们编写好的应用程序,经过严格的测试之后都会上线运行在CentOS服务器上,当然了,你可能会使用其它类型的OS服务器,但这里我们一CentOS 7版本为例,因为centOS更有代表性。

你可以在公众号后台私信 gdal,获取在CentOS 7平台编译gdal需要的依赖包。依赖包如下图:

image.png

如果这个版本你觉得比较低,你也可以去这里下载你想过要的gdal源码包版本,然后去寻找对应版本的geos和proj依赖包。如果这里你有任何疑惑,可以私信我交流~

在你开始进行编译前得先确保服务器具有gcc-c++、gcc、swig、ant的 基础环境,如果没有那就使用yum 工具在线安装即可。如果是离线安装,依赖环境需要从安装镜像里面去下载安装。你需要把安装进行挂载至服务器上,然后进行安装这些环境,如果你有任何疑问可以私信与我交流。

  • 编译Proj

    <pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="" cid="n93" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 0px; padding-right: 1ch; width: inherit;"> #进入gdal所在目录开始执行以下命令:
    [root@wb gdal]# tar -zxvf proj-4.9.3.tar.gz
    [root@wb gdal]# cd proj-4.9.3/
    [root@wb proj-4.9.3]# ./configure
    [root@wb proj-4.9.3]# make
    [root@wb proj-4.9.3]# make install</pre>

  • 编译Geos

    <pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="" cid="n96" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 0px; padding-right: 1ch; width: inherit;"> #进入Geos目录,开始执行以下命令:
    [root@wb gdal]# tar -jxvf geos-3.6.2.tar.bz2
    [root@wb gdal]# cd geos-3.6.2/
    [root@wb geos-3.6.2]# ./configure
    [root@wb geos-3.6.2]# make
    [root@wb geos-3.6.2]# make instal</pre>

  • 编译gdal

    <pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="" cid="n99" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 0px; padding-right: 1ch; width: inherit;"> #进入gdal目录,开始执行以下命令:
    [root@bogon gdal]# tar -zxvf gdal-2.1.3.tar.gz
    [root@bogon gdal]# cd gdal-2.1.3
    [root@bogon gdal-2.1.3]# ./configure
    [root@bogon gdal-2.1.3]# make
    [root@bogon gdal-2.1.3]# make install</pre>

    经过一段时间编译完成后,编辑 swig/java/java.opt,配置 JAVA_HOME 路径。

    <pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="" cid="n101" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); --select-text-bg-color: #36284e; --select-text-font-color: #fff; font-size: 0.9rem; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(218, 218, 218); position: relative !important; margin-bottom: 3em; margin-left: 2em; padding-left: 0px; padding-right: 1ch; width: inherit;"> JAVA_HOME = /usr/java/jdk1.8.0_151

    JAVADOC=$(JAVA_HOME)/bin/javadoc

    JAVAC=$(JAVA_HOME)/bin/javac

    JAVA=$(JAVA_HOME)/bin/java

    JAR=$(JAVA_HOME)/bin/jar

    JAVA_INCLUDE=-I(JAVA_HOME)/include -I(JAVA_HOME)/include/linux</pre>

之后编译 gdal 的 java 库:

[root@wb gdal-2.1.3]#cd swig/java

[root@wb java]# make

编译完成后,在 “……/gdal-2.1.3/swig/java”目录 下生成 gdal.jar 和

libgdalconstjni.so、libgdaljni.so、libogrjni.so、libosrjni.so 等 4 个 so 文件。

拷贝 4 个 so 文件到 java.library.path 路径中。

  • 添加GDAL_DATA

    添加 GDAL_DATA 环境变量:

    [root@centos7]# vim /etc/profile

    在最后插入一行,内容如下:

    export GDAL_DATA=/usr/share/gdal

    让配置生效:

    [root@centos7]# source /etc/profile

    其中,/usr/share/gdal 内容如下,若无可将“……/gdal-2.1.3/data”复制到此

    处:


    image.png

    至此,我们已经在CentOS平台下编译好了gdal运行环境,如果你有任何问题,可以私信我交流。

GDAL开发实战

你可以选择新建一个SpringBoot工程,也可以使用你已有的工程,前提是你得完成前面我们提到的环境搭建的工作,确保我们能在SpringBoot工程中可以引用gdal的功能接口;

为了方便起见,我建议你使用已有的SpringBoot工程进行开始我们的学习,我自己就是在一个专门进行新技术学习的SpringBoot工程里面进行学习。

实战1 读取影像的元信息

在下面的代码中,我们对影响做了元信息的提取,并对影像的元信息进行了简单的处理和组装,并返回这些结果。

package com.bin.utils.gis;

import com.alibaba.fastjson.JSONObject;
import com.geovis.bin.utils.DateUtil;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.Driver;
import org.gdal.gdal.gdal;
import org.gdal.gdalconst.gdalconstConstants;
import java.io.File;

/**
 * @Author Wangb
 * @Date 2021/11/6 22:08.
 */
public class GDALUtil {

    /**
     * 读取影像元信息
     *
     * @param fileName
     * @return
     */
    public static JSONObject parseImage(String fileName) {
        JSONObject result = new JSONObject();
        try {
            //gdal使用前的所必须要有的注册语句
            gdal.AllRegister();
            long start = System.currentTimeMillis();
            // 读取影像数据
            Dataset dataset = gdal.Open(fileName, gdalconstConstants.GA_ReadOnly);
            if (dataset == null) {
                System.err.println("GDALOpen failed - " + gdal.GetLastErrorNo());
                System.err.println(gdal.GetLastErrorMsg());
                return null;
            }

            Driver driver = dataset.GetDriver();
            //System.out.println("Driver: " + driver.getShortName() + "/" + driver.getLongName());
            // 读取影像信息
            String satellite_type = "CAR-" + fileName.substring(103, 105);
            resultJp2.put("卫星名称",satellite_type);
            result.put("参考坐标系标识", dataset.GetProjectionRef());
            result.put("坐标系标识", dataset.GetProjection());
            result.put("类型", driver.getShortName());
            result.put("列", dataset.getRasterXSize());
            result.put("行", dataset.getRasterYSize());
            result.put("band", dataset.getRasterCount());
            result.put("pixelsType", dataset.GetRasterBand(1).getDataType());// 像素类型

            double[] transform = dataset.GetGeoTransform();
            result.put("MinX", transform[0]);
            result.put("MaxX", (transform[0] + dataset.getRasterXSize() * transform[1]));
            result.put("MinY", (transform[3] - dataset.getRasterYSize() * transform[1]));
            result.put("MaxY", transform[3]);
            result.put("resolution", transform[1]); // 分辨率

            // 卫星四至坐标
            String geom = String.format("Polygon((%s %s,%s %s,%s %s,%s %s ,%s %s ))",
                    Double.parseDouble(result.getString("MaxX")),
                    Double.parseDouble(result.getString("MinY")),
                    Double.parseDouble(result.getString("MaxX")),
                    Double.parseDouble(result.getString("MaxY")),
                    Double.parseDouble(result.getString("MinX")),
                    Double.parseDouble(result.getString("MaxY")),
                    Double.parseDouble(result.getString("MinX")),
                    Double.parseDouble(result.getString("MinY")),
                    Double.parseDouble(result.getString("MaxX")),
                    Double.parseDouble(result.getString("MinY"))

            );

            // 获取文件拍摄时间
            File file = new File(fileName);
            String shotTime = file.getName().substring(36, 40) + "-" + file.getName().substring(34, 36) + "-" + file.getName().substring(32, 34);

            // 获取卫星名称
            String satelliteName = file.getName().substring(0, 3);

            // 获取拍摄中心点坐标
            String center = String.format("POINT(%s %s)",
                    (transform[0] + (transform[0] - dataset.getRasterYSize() * transform[1])) / 2,
                    (transform[3] + (transform[3] - dataset.getRasterYSize() * transform[1])) / 2
            );
            // 获取文件格式
            String format = fileName.substring(fileName.lastIndexOf(".") + 1);

            result.put("center", center);
            result.put("geom", geom);

            result.put("shotTime", DateUtil.parseDate(shotTime));
            result.put("satelliteName", "CAR-" + satelliteName.substring(1, 3));
            result.put("format", format);

           String projection = dataset.GetProjection();
                                            System.out.println(gdalconstConstants.GDT_Byte);
            dataset.delete();
            long end = System.currentTimeMillis();
            System.out.println(end - start);
            gdal.GDALDestroyDriverManager();
            return result;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }

    public static void main(String[] args) {
        parseImage("D:\\01_资料\\GIS\\GF1_PMS1_E121.3_N39.7_20170429_L1A0002339797/GF1_PMS1_E121.3_N39.7_20170429_L1A0002339797-MSS1.tiff");
    }
}

获取影像元信息的程序运行结果如下图所示:


image.png

实战2 影像的裁切

package com.geovis.bin.utils.gis;

import com.alibaba.fastjson.JSONObject;
import com.geovis.bin.utils.DateUtil;
import org.gdal.gdal.Band;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.Driver;
import org.gdal.gdal.gdal;
import org.gdal.gdalconst.gdalconst;
import org.gdal.gdalconst.gdalconstConstants;

import java.io.File;

/**
 * @Author Wangb
 * @Date 2021/6/11 19:08.
 */
public class GDALUtil {
    public static synchronized void  cutImage(String fileName) {
        gdal.AllRegister();
        Dataset rds = gdal.Open(fileName, gdalconst.GA_ReadOnly);
        //宽、高、波段数
        int b = rds.getRasterCount();
        //从波段中获取影像的数据类型,gdal中波段索引从1开始
        int dataType = rds.GetRasterBand(1).GetRasterDataType();

        //六参数信息
        double[] geoTransform = rds.GetGeoTransform();

        /**
         * 设置要裁剪的起始像元位置,以及各方向的像元数
         * 这里表示从像元(5000, 5000)开始,x方向和y方向各裁剪1000个像元
         * 最终结果就是一幅1000*1000的影像
         */
        int startX = 5000;
        int startY = 5000;
        int clipX = 1000;
        int clipY = 1000;

        //计算裁剪后的左上角坐标
        geoTransform[0] = geoTransform[0] + startX * geoTransform[1] + startY * geoTransform[2];
        geoTransform[3] = geoTransform[3] + startX * geoTransform[4] + startY * geoTransform[5];

        //创建结果图像
        Driver driver = gdal.GetDriverByName("GTIFF");
        String file = "D:\\01_资料\\GIS\\test.tiff";
        Dataset outputDs = driver.Create(file, clipX, clipY, b, dataType);
        outputDs.SetGeoTransform(geoTransform);
        outputDs.SetProjection(rds.GetProjection());

        try {
            //按band读取
            for (int i = 0; i < clipY; i++) {
                //按行读取
                for (int j = 1; j <= b; j++) {
                    Band orgBand = rds.GetRasterBand(j);
                    int[] cache = new int[clipX];
                    //从位置x开始,只读一行
                    orgBand.ReadRaster(startX, startY + i, clipX, 1, cache);
                    Band desBand = outputDs.GetRasterBand(j);
                    //从左上角开始,只写一行
                    desBand.WriteRaster(0, i, clipX, 1, cache);
                    desBand.FlushCache();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            //释放资源
            rds.delete();
            outputDs.delete();
        }

    }

    public static void main(String[] args) {
        String fileName = "D:\\01_资料\\GIS\\GF1_PMS1_E121.3_N39.7_20170429_L1A0002339797/GF1_PMS1_E121.3_N39.7_20170429_L1A0002339797-MSS1.tiff";

//        parseImage(fileName);
        cutImage(fileName);
    }
}

影像裁切的运行截图如下,虽然成功的进行了影像的裁切,但是控制台会爆红,原因暂时还不清楚,有知道的小伙伴可以私信一下哈~

image.png

值得注意的是不论你是获取影像的元信息还是进行影像的裁切,都应该对方法进行并发控制,最简单的实现就是加上synchronized关键字,这样做虽然影响了并发时性能,但是却避免了OOM异常,因为每一次读取影像,都会把整个tif读进内存。

点击跳转至gdal API手册

后面我会继续进行GDAL其它常用功能的学习,也希望这篇文章可以帮助到你···

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容