Skip to content

GDAL Java 绑定使用指南

简介

GDAL (Geospatial Data Abstraction Library) 是一个开源的地理空间数据转换库。Java 绑定允许在 Java 应用程序中使用 GDAL 的功能。

安装步骤

1. 系统要求

  • Java Development Kit (JDK) 8 或更高版本
  • Maven 或 Gradle 构建工具
  • 原生 GDAL 库

2. 安装原生 GDAL 库

Windows

bash
# 使用 OSGeo4W 安装
# 下载并运行 OSGeo4W 安装程序
# 选择安装 GDAL 组件

Linux (Ubuntu/Debian)

bash
sudo apt-get update
sudo apt-get install gdal-bin libgdal-dev

macOS

bash
brew install gdal

3. 配置环境变量

Windows

batch
set GDAL_HOME=C:\OSGeo4W
set PATH=%PATH%;%GDAL_HOME%\bin
set JAVA_LIBRARY_PATH=%GDAL_HOME%\bin

Linux/macOS

bash
export GDAL_HOME=/usr
export LD_LIBRARY_PATH=$GDAL_HOME/lib:$LD_LIBRARY_PATH
export JAVA_LIBRARY_PATH=$GDAL_HOME/lib

4. Maven 依赖

xml
<dependencies>
    <!-- GDAL 官方 Java 绑定(纯 Java jar,不含原生库) -->
    <dependency>
        <groupId>org.gdal</groupId>
        <artifactId>gdal</artifactId>
        <version>3.11.0</version>
    </dependency>

    <!-- gdal-utils:官方提供的配套 jar(随 org.gdal:gdal 一同发布),
         包含若干辅助工具类。按需引入,版本与 gdal 保持一致。 -->
    <dependency>
        <groupId>org.gdal</groupId>
        <artifactId>gdal-utils</artifactId>
        <version>3.11.0</version>
    </dependency>
</dependencies>

注意:

  • org.gdal:gdal 只是 JNI 桥接的 Java 类(SWIG 生成),jar 里不包含 gdalall_gdal.dll / libgdal.so 等原生库,必须单独安装(见上一步),且大版本号要和原生库一致(jar 是 3.11.x 就配 GDAL 3.11.x 的原生库)。
  • 另一个常见选择是 GeoSolutions 的 jaiext-gdal(it.geosolutions.jaiext:jaiext-gdal),它把 GDAL 包装成 JAI RenderedOp,适合做影像处理管线;本笔记以官方绑定为准。

5. Gradle 依赖

groovy
dependencies {
    implementation 'org.gdal:gdal:3.11.0'
}

6. 原生库加载方式

Java 绑定通过 JNI 调用原生 GDAL,有三种常见加载方式:

  1. -Djava.library.path(最常用):把 GDAL 的 lib/bin 目录加进去,见下文"常见问题"。
  2. JNA/JNI 自动定位:部分发行版(如 gdal-utils 附带工具类)支持从 classpath 解压 dll/so 后 System.load()。
  3. 打包进 fat-jar:不推荐;dll 需要落盘才能被 JVM 加载,通常用 maven-dependency-plugin 在启动前解到临时目录。

验证安装是否成功:

java
gdal.AllRegister();
System.out.println(gdal.VersionInfo("VERSION_NUM"));   // 例如 3110000 = 3.11.0

基本用法

初始化 GDAL

java
import org.gdal.gdal.gdal;
import org.gdal.ogr.ogr;

public class GDALBootstrap {

    public static void main(String[] args) {
        // 注册全部栅格驱动(gdal.AllRegister 在较新版本中为推荐入口;
        // 老版本用 gdal.RegisterAll())
        gdal.AllRegister();
        // 注册全部矢量(OGR)驱动
        ogr.RegisterAll();

        System.out.println("GDAL Version: " + gdal.VersionInfo("VERSION_NUM"));
    }
}

约定:下面所有示例都假定已完成上述初始化。每个类里写一次即可(例如放在 main 或 Spring 的 @PostConstruct 里)。

读取栅格数据

java
import org.gdal.gdal.Band;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.gdal;
import org.gdal.gdalconst.gdalconstConstants;
import org.gdal.osr.SpatialReference;

public class ReadRaster {

    public static void readRaster(String filePath) {
        // GA_ReadOnly = 0;也可省略第二个参数(默认只读)
        try (Dataset dataset = gdal.OpenEx(filePath,
                gdalconstConstants.OF_RASTER | gdalconstConstants.OF_VERBOSE_ERROR)) {
            if (dataset == null) {
                System.err.println("无法打开文件: " + filePath);
                return;
            }

            int xSize = dataset.getRasterXSize();
            int ySize = dataset.getRasterYSize();
            int bandCount = dataset.getRasterCount();
            System.out.println("尺寸: " + xSize + " x " + ySize);
            System.out.println("波段数: " + bandCount);

            // 地理变换:[左上角X, 像素宽, 行旋转, 左上角Y, 列旋转, 像素高(负值)]
            double[] gt = dataset.GetGeoTransform();
            System.out.printf("范围: (%.6f, %.6f) ~ (%.6f, %.6f)%n",
                    gt[0], gt[3] + ySize * gt[5],
                    gt[0] + xSize * gt[1], gt[3]);

            // 空间参考(WKT / EPSG)
            SpatialReference srs = dataset.GetSpatialRef();
            if (srs != null) {
                int epsg = -1;
                srs.AutoIdentifyEPSG();
                epsg = srs.GetAuthorityCode(null);
                System.out.println("坐标系: EPSG:" + epsg + " / " + srs.ExportToWkt());
                srs.delete();
            }

            // 读取第 1 波段(注意:buffer 类型必须与波段数据类型一致,
            // Float32 波段要用 float[],Byte 波段用 byte[],混用会报错)
            Band band = dataset.GetRasterBand(1);
            System.out.println("数据类型: " + band.GetRasterDataType()
                    + ", NoData=" + band.GetNoDataValue(true));

            float[] data = new float[xSize * ySize];
            band.ReadRaster(0, 0, xSize, ySize, data);

            // 统计信息(false=不强制重算,true=覆盖已有缓存)
            double[] stats = band.GetStatistics(false, true);
            System.out.printf("min=%.2f max=%.2f mean=%.2f std=%.2f%n",
                    stats[0], stats[1], stats[2], stats[3]);
        }
        // try-with-resources:Dataset 实现了 AutoCloseable,close() 内部调用 delete()
    }
}

要点:

  • gdal.Open vs gdal.OpenEx:OpenEx 支持 flags(如 OF_VECTOR、OF_RASTER、OF_READONLY),还能直接打开带子数据集的格式(如 file.tif/subdataset=1)。
  • ReadRaster 一次性读整幅大图容易 OOM,生产环境按块读(见"性能优化")。
  • 所有返回的 SWIG 包装对象(Dataset、Band、SpatialReference、Feature…)都持有原生内存,用完必须 delete()(或依赖 try-with-resources)。

写入栅格数据

java
import org.gdal.gdal.Band;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.Driver;
import org.gdal.gdal.gdal;
import org.gdal.gdalconst.gdalconstConstants;
import org.gdal.osr.SpatialReference;

public class WriteRaster {

    public static void writeRaster(String outputPath, int width, int height) {
        try (Driver driver = gdal.GetDriverByName("GTiff")) {
            // 创建选项:分块 + LZW 压缩(对 Web 服务读取很关键)
            String[] createOpts = { "TILED=YES", "BLOCKXSIZE=256", "BLOCKYSIZE=256",
                                    "COMPRESS=LZW" };
            try (Dataset dataset = driver.Create(outputPath, width, height, 1,
                    gdalconstConstants.GDT_Float32, createOpts)) {

                // 地理变换:[originX, pixelWidth, 0, originY, 0, -pixelHeight]
                // 注意第 5 个值(像素高)必须为负,表示 y 轴向下递减
                double pixelSize = 1.0;
                dataset.SetGeoTransform(new double[] { 0, pixelSize, 0,
                        height * pixelSize, 0, -pixelSize });

                // 坐标系:EPSG:4326
                SpatialReference srs = new SpatialReference();
                srs.importFromEPSG(4326);
                dataset.SetProjection(srs.ExportToWkt());
                srs.delete();

                // 写入数据
                Band band = dataset.GetRasterBand(1);
                band.SetNoDataValue(-9999.0);
                float[] data = new float[width * height];
                for (int i = 0; i < data.length; i++) data[i] = i % 1000 / 10f;
                band.WriteRaster(0, 0, width, height, data);

                // 刷新缓存到磁盘(close 也会做,但显式调用可提前落盘、便于出错定位)
                dataset.FlushCache();
            }
        }
    }
}

要点:

  • SetGeoTransform 的六个参数含义是 [c0, c1, c2, c3, c4, c5],对应 x = c0 + col*c1 + row*c2、y = c3 + col*c4 + row*c5。常规北向上影像就是 [minX, 分辨率, 0, maxY, 0, -分辨率]。
  • driver.Create(...) 的最后一个参数 GDT_* 决定波段数据类型;多波段传 band 数即可。
  • 大文件加 BIGTIFF=IF_SAFER;发布到地图服务建议 TILED=YES + 压缩 + 金字塔(见下文)。

矢量数据操作

java
import org.gdal.ogr.DataSource;
import org.gdal.ogr.Feature;
import org.gdal.ogr.Geometry;
import org.gdal.ogr.Layer;
import org.gdal.ogr.ogr;

public class ReadVector {

    public static void readShapefile(String filePath) {
        try (DataSource dataSource = ogr.OpenEx(filePath, 0)) {
            if (dataSource == null) {
                System.err.println("无法打开文件: " + filePath);
                return;
            }

            int layerCount = dataSource.GetLayerCount();
            for (int i = 0; i < layerCount; i++) {
                Layer layer = dataSource.GetLayer(i);
                System.out.println("图层: " + layer.GetName()
                        + ", 几何类型: " + layer.GetGeomType());

                layer.ResetReading();
                Feature feature;
                while ((feature = layer.GetNextFeature()) != null) {
                    Geometry geom = feature.GetGeometryRef();
                    if (geom != null) {
                        System.out.println("  FID=" + feature.GetFID()
                                + " " + geom.GetGeometryName());
                        geom.delete();   // GetGeometryRef 返回的是引用,需手动释放
                    }
                    // 读属性:按字段名取(注意区分大小写)
                    String name = feature.GetFieldAsString("name");
                    double val = feature.GetFieldAsDouble("value");
                    feature.delete();
                }
                layer.delete();
            }
        }
    }
}

创建矢量数据

java
import org.gdal.ogr.DataSource;
import org.gdal.ogr.Driver;
import org.gdal.ogr.FieldDefn;
import org.gdal.ogr.Feature;
import org.gdal.ogr.FeatureDefn;
import org.gdal.ogr.Geometry;
import org.gdal.ogr.Layer;
import org.gdal.ogr.ogr;
import org.gdal.osr.SpatialReference;

public class WriteVector {

    public static void createShapefile(String outputPath) throws Exception {
        try (Driver driver = ogr.GetDriverByName("ESRI Shapefile")) {
            try (DataSource ds = driver.CreateDataSource(outputPath)) {
                SpatialReference srs = new SpatialReference();
                srs.importFromEPSG(4326);

                // wkbPoint = 1(org.gdal.ogr.ogr.wkbPoint)
                Layer layer = ds.CreateLayer("points", srs, ogr.wkbPoint);
                FieldDefn field = new FieldDefn("name", ogr.OFTString);
                field.SetWidth(64);
                layer.CreateField(field);
                field.delete();

                FeatureDefn defn = layer.GetLayerDefn();
                try (Feature feat = new Feature(defn)) {
                    feat.SetField("name", "Test Point");
                    Geometry point = new Geometry(ogr.wkbPoint);
                    point.AddPoint_2D(116.4, 39.9);
                    feat.SetGeometry(point);
                    layer.CreateFeature(feat);
                }
                layer.SyncToDisk();
                srs.delete();
            }
        }
    }
}

要点:Shapefile 编码默认 ANSI,中文建议用 GeoPackage (GPKG) 或 GeoJSON 驱动替代;CreateLayer 的第三个参数是 OGR wkb 几何类型常量。

坐标转换

java
import org.gdal.osr.CoordinateTransformation;
import org.gdal.osr.SpatialReference;

public class CoordinateTransform {

    /** 批量点转换:一次建转换器,循环内只调 TransformPoint(推荐做法) */
    public static double[][] transformBatch(double[][] points, int srcEpsg, int dstEpsg) {
        SpatialReference src = new SpatialReference();
        src.importFromEPSG(srcEpsg);
        SpatialReference dst = new SpatialReference();
        dst.importFromEPSG(dstEpsg);

        try (CoordinateTransformation ct = new CoordinateTransformation(src, dst)) {
            double[][] out = new double[points.length][2];
            for (int i = 0; i < points.length; i++) {
                // TransformPoint 会原地修改传入数组,返回 [x, y, z]
                double[] p = { points[i][0], points[i][1], 0 };
                ct.TransformPoint(p);
                out[i][0] = p[0];
                out[i][1] = p[1];
            }
            return out;
        } finally {
            src.delete();
            dst.delete();
        }
    }

    public static void main(String[] args) {
        // WGS84 -> Web Mercator
        double[][] res = transformBatch(new double[][] { { 116.4, 39.9 } }, 4326, 3857);
        System.out.printf("WebMercator: (%.2f, %.2f)%n", res[0][0], res[0][1]);
    }
}

要点:

  • CoordinateTransformation 构建有成本(要加载网格/参数),不要在循环里反复 new;跨多个 SRS 对时可用 Map<Pair, CoordinateTransformation> 缓存。
  • 常用 EPSG:4326 WGS84 经纬度、3857 Web Mercator(Google/OSM 瓦片)、4490 CGCS2000、32650 UTM 50N;国内常见地方坐标系用 EPSG:4610~4619 系列(CGCS2000 / 北京54 / 西安80 的 3 度带与高斯投影,具体带号按所在经度查)。
  • 更高精度场景可给 SpatialReference 设置七参数(SetTOWGS84)或自定义 gridshift。

高级操作

重投影栅格(gdal.Warp)

Java 绑定把 C++ 的 warp 能力暴露为 gdal 类的静态方法,选项用 String[] 传,键名与 gdalwarp CLI 相同(不带前导 -):

java
import org.gdal.gdal.Dataset;
import org.gdal.gdal.gdal;

public class Reproject {

    public static void reproject(String in, String out, int dstEpsg) {
        String[] options = {
            "dstSRS=EPSG:" + dstEpsg,   // 目标坐标系
            "r=BILINEAR",               // 重采样: NEAREST / BILINEAR / CUBIC / LANCZOS
            "format=GTiff",
            "co=TILED=YES", "co=COMPRESS=LZW"
        };
        // 一步写出新文件(对应 CLI: gdalwarp -t_srs EPSG:xxxx in.tif out.tif)
        try (Dataset src = gdal.Open(in)) {
            try (Dataset dst = gdal.Warp(out, src, options)) {
                // dst 非空即成功;可继续读它的元数据
            }
        }
    }
}
  • 等价 CLI 对照:gdalwarp -t_srs EPSG:3857 -r bilinear in.tif out.tif。
  • 常用选项:te=(目标 extent,四个值按 minx maxy 顺序给的是 left bottom right top,注意与 Python gdal.WarpOptions 的 outputBounds 语义一致)、tr=(目标分辨率)、srcNodata= / dstNodata=、cutline=、cropToCutline=YES、multi=CREATE_ONLY(多线程分块写)。
  • 需要精细控制输出尺寸/对齐时,可用 gdal.AutoCreateWarpedDataset 先算出目标数据集再 WarpCreateCopy(签名随版本略有差异,以所用 jar 的 javadoc 为准)。

按矢量边界裁剪

java
String[] options = {
    "cutlineDSName=" + clipShp,          // 边界 shp / gpkg / geojson
    "cropToCutline=YES",                  // 输出范围收缩到 cutline 的最小外包框
    "srcNodata=0",
    "dstAlpha=YES"                        // 加 alpha 波段做透明遮罩(可选)
};
try (Dataset src = gdal.Open(rasterPath)) {
    try (Dataset dst = gdal.Warp("clipped.tif", src, options)) { }
}

合并多幅栅格(镶嵌)

推荐做法:先 gdalbuildvrt 生成 VRT,再 gdalwarp/Translate 落地 —— VRT 只是描述性 XML,不复制像素,几百上千幅图也不怕。Java 里通常分两步:

java
import java.io.*;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.gdal;

public class Mosaic {

    /** 调用 gdalbuildvrt CLI 生成 .vrt(需 gdal 在 PATH 中) */
    public static void buildVrt(String vrtPath, String... rasters) throws Exception {
        ProcessBuilder pb = new ProcessBuilder(
                "gdalbuildvrt", "-q", "-resolution", "average", vrtPath);
        for (String r : rasters) pb.command().add(r);
        pb.redirectErrorStream(true);
        Process p = pb.start();
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(p.getInputStream()))) {
            String line; while ((line = br.readLine()) != null) System.err.println(line);
        }
        if (p.waitFor() != 0) throw new IOException("gdalbuildvrt failed");
    }

    /** 把 VRT 翻译成实体 GeoTIFF */
    public static void vrtToTiff(String vrtPath, String outPath) {
        try (Dataset vrt = gdal.Open(vrtPath)) {
            try (Dataset out = gdal.Translate(outPath, vrt, new String[] {
                    "format=GTiff", "co=TILED=YES", "co=COMPRESS=LZW" })) { }
        }
    }
}

Java 绑定也有 gdal.BuildVRT(...) 静态方法(返回 VRTDataset),但重载较多、随版本变化,写死签名容易踩坑;生产代码里用上面的 CLI + Translate 组合最稳。纯 Java 不想依赖 CLI 时,也可直接用下面的 Warp 合并。

不用 VRT 的替代方案:gdal.Warp 直接吃多源(对应 Python 里把 list 传给 gdal.Warp):

java
String[] files = { "tile1.tif", "tile2.tif", "tile3.tif" };
Dataset[] srcs = new Dataset[files.length];
for (int i = 0; i < files.length; i++) srcs[i] = gdal.Open(files[i]);
try {
    try (Dataset dst = gdal.Warp("mosaic.tif", srcs, new String[] {
            "format=GTiff", "r=NEAREST", "co=TILED=YES", "co=COMPRESS=LZW" })) { }
} finally {
    for (Dataset d : srcs) if (d != null) d.delete();
}

生成金字塔(overviews)

java
import org.gdal.gdal.Dataset;
import org.gdal.gdal.gdal;
import org.gdal.gdalconst.gdalconstConstants;

try (Dataset ds = gdal.OpenEx("large_image.tif", gdalconstConstants.GA_Update)) {
    // NEAREST 适合分类/离散数据;BILINEAR/AVERAGE 适合连续影像
    int ok = ds.BuildOverviews("NEAREST", new int[] { 2, 4, 8, 16, 32 });
    System.out.println(ok == 0 ? "金字塔生成成功" : "金字塔生成失败");
}

发布给 Web 地图服务(MapServer/GeoServer/自研切片)前,标准动作是:TILED=YES + COMPRESS + BuildOverviews。

格式转换(gdal.Translate)

java
try (Dataset src = gdal.Open("input.jp2")) {
    String[] options = {
        "format=GTiff",
        "co=COMPRESS=LZW", "co=TILED=YES",
        "a_nodata=-9999",           // 设置 NoData
        "scale_min=0", "scale_max=255"  // 可选:拉伸取值范围
    };
    try (Dataset dst = gdal.Translate("output.tif", src, options)) { }
}

批量转换时外层套 Files.list() / glob 即可;大文件建议 multi=CREATE_ONLY 并行分块。

常见问题解决

1. 找不到原生库

错误: java.lang.UnsatisfiedLinkError

解决方案:

  • 确保 GDAL 原生库已正确安装
  • 设置 java.library.path JVM 参数:
    bash
    java -Djava.library.path=/path/to/gdal/lib -jar your-app.jar

2. 驱动程序未注册

错误: 无法打开特定格式的文件

解决方案:

java
// 在使用前调用
gdal.AllRegister();
ogr.RegisterAll();

3. 内存管理

GDAL Java 绑定使用原生资源,需要手动释放:

java
Dataset ds = gdal.Open("file.tif", gdalconstConstants.GA_ReadOnly);
try {
    // 使用数据集
} finally {
    ds.delete();  // 重要:释放原生资源
}

性能优化建议

1. 分块读取大栅格(防 OOM)

java
import org.gdal.gdal.Band;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.gdal;

public static void readInChunks(String path, int chunk, java.util.function.BiConsumer<float[], int[]> consumer) {
    try (Dataset ds = gdal.Open(path)) {
        Band band = ds.GetRasterBand(1);
        int w = ds.getRasterXSize(), h = ds.getRasterYSize();
        float[] buf = new float[chunk * chunk];   // 复用同一块 buffer
        for (int y0 = 0; y0 < h; y0 += chunk) {
            int ys = Math.min(chunk, h - y0);
            for (int x0 = 0; x0 < w; x0 += chunk) {
                int xs = Math.min(chunk, w - x0);
                band.ReadRaster(x0, y0, xs, ys, buf);
                consumer.accept(buf, new int[] { x0, y0, xs, ys });
            }
        }
    }
}
  • chunk 取驱动块大小的整数倍(GTiff 默认 256×256)命中率最高。
  • 多波段数据按"整行所有波段一起读"组织循环,减少 IO 次数。

2. 多线程并行处理瓦片

GDAL 的 Dataset/Band 对象不是线程安全的,正确姿势是"每线程独立 Open 自己的子窗口":

java
ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
List<Future<?>> futures = new ArrayList<>();
for (int tile : tiles) {
    futures.add(pool.submit(() -> {
        try (Dataset ds = gdal.Open(basePath)) {          // 每线程独立打开
            processTile(ds, tile);
        }
    }));
}
futures.forEach(f -> f.get());
pool.shutdown();

配合 warp 时的 multi=CREATE_ONLY、写 GTiff 时的 NUM_THREADS=ALL_CPUS 创建选项,可进一步压榨单任务内部并行度。

3. 其他要点

  1. 重用对象:CoordinateTransformation、Driver 等构建成本高,缓存复用,别在循环里 new。
  2. 创建选项:发布用影像固定加 TILED=YES + COMPRESS=LZW/DEFLATE + 金字塔;大数据量加 BIGTIFF=IF_SAFER。
  3. 内存映射:只读场景可用 MEM: 驱动把小数据集整体载入内存,后续读写全走 RAM。
  4. 日志:gdal.SetConfigOption("CPL_LOG", "/tmp/gdal.log") 或 "CPL_DEBUG" 排查驱动问题。

参考资源

GDAL Java 绑定(Java Bindings)全面技术总结

基于 OSGeo/gdal 官方仓库、Context7 文档与公开技术资料整理,涵盖架构、安装、API、示例代码与最佳实践。


目录

  1. 概述
  2. 核心概念与架构
  3. 环境搭建与依赖管理
  4. 栅格数据 API(Raster)
  5. 矢量数据 API(OGR Vector)
  6. 空间参考与坐标变换(OSR)
  7. 常用工具类与回调机制
  8. 完整示例程序
  9. 性能优化与线程安全
  10. 常见问题与故障排查
  11. 版本演进与兼容性
  12. 参考资料

1. 概述

1.1 什么是 GDAL

GDAL(Geospatial Data Abstraction Library) 是一个在 X/MIT 许可协议下的开源地理空间数据转换库,最初作为 C/C++ 库开发,提供对 200+ 种栅格格式 和 100+ 种矢量格式 的统一读写接口。其核心组件包括:

组件说明
GDAL(gcore)栅格数据抽象层,处理影像的读写、波段操作、重采样等
OGR矢量数据抽象层(Simple Features 模型),处理点/线/面要素及属性表
OSR / PROJ空间参考系统(Spatial Reference System)与坐标投影变换引擎
CPL(port)底层平台支持库,封装文件 I/O、字符串、网络请求等跨平台功能
alg算法库,包含重采样、直方图、地形分析(坡度/坡向/山体阴影)等
drivers(frmts)各格式的驱动实现,每个格式一个驱动插件

1.2 什么是 Java 绑定

GDAL 官方通过 SWIG(Simplified Wrapper and Interface Generator)自动生成 Java 绑定,生成产物为:

  • gdal.jar — 纯 Java 类库,包含 org.gdal.* 包下的所有包装类
  • libgdalalljni.so / gdalalljni.dll / libgdalalljni.dylib — JNI 本地库,桥接 Java 与底层 C++ libgdal

Java 绑定中的类与方法命名基本与 C++ 原类一一对应,因此熟悉 GDAL C++ API 的开发者可以平滑迁移。

重要提示:由于 Java GC 运行在主线程之外的独立线程中,GDAL 必须配置为多线程支持编译,否则即使单线程使用也可能出现崩溃。这是使用 Java 绑定时最容易被忽略的前提条件。

1.3 典型应用场景

  • 遥感影像批量读取、格式转换、裁剪、重投影
  • 多源矢量数据(Shapefile / GeoJSON / GeoPackage / GDB)导入导出
  • GIS 服务端(GeoServer、ArcGIS Enterprise 扩展)的空间数据处理后端
  • 地理空间 ETL 管道中的统一数据访问层
  • 科学计算(NetCDF、HDF5、GRIB)与 GIS 数据的互操作

2. 核心概念与架构

2.1 对象层次结构

┌─────────────────────────────────────────────────────────────┐
│                        栅格侧(GDAL gcore)                   │
│                                                             │
│  gdal (静态入口)                                             │
│   ├── AllRegister()          注册所有驱动                    │
│   ├── Open(filename, flags)  打开数据集                      │
│   └── GetDriverByName(name)  按名称获取驱动                  │
│                                                             │
│  Driver                                                            │
│   ├── Create(...)/CreateCopy(...)                                │
│   └── OpenDataset(...)                                           │
│                                                             │
│  Dataset                                                           │
│   ├── getRasterXSize() / getRasterYSize()                       │
│   ├── getRasterCount()                                           │
│   ├── GetProjectionRef() → String (WKT)                         │
│   ├── GetGeoTransform(double[6])                                 │
│   ├── GetRasterBand(bandIndex) → Band   (1-based!)              │
│   ├── SetGeoTransform(...)                                       │
│   └── FlushCache()                                               │
│                                                             │
│  Band                                                                │
│   ├── ReadRaster(...) / WriteRaster(...)                          │
│   ├── ReadBlock(xOff, yOff, dataBuffer)                           │
│   ├── GetStatistics(...)                                          │
│   ├── ComputeRasterMinMax(...)                                    │
│   ├── GetMaskBand() / GetMaskFlags()                              │
│   ├── GetOverview(i) → Band                                       │
│   └── SetNoDataValue(dfVal)                                       │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      矢量侧(OGR)                            │
│                                                             │
│  ogr (静态入口)                                              │
│   └── RegisterAll()         注册所有矢量驱动                 │
│                                                             │
│  Driver (org.gdal.ogr.Driver)                                │
│   └── Open(name, bUpdate) → DataSource                        │
│   └── CreateDataSource(...)                                   │
│                                                             │
│  DataSource                                                    │
│   ├── GetLayer(count) → Layer                                  │
│   ├── ExecuteSQL(stmt, spatialFilter, dialect) → Layer         │
│   └── ReleaseDS()                                              │
│                                                             │
│  Layer                                                         │
│   ├── GetNextFeature() → Feature                               │
│   ├── ResetReading()                                           │
│   ├── CreateFeature(feature)                                   │
│   ├── GetGeomFieldDefn(i)                                      │
│   └── SetSpatialFilter(geometry)                               │
│                                                             │
│  Feature                                                       │
│   ├── GetFieldDefn(i) → FieldDefn                              │
│   ├── GetFieldAsString/AsDouble/AsInteger(i)                   │
│   ├── SetField(name, value)                                    │
│   ├── GetGeometryRef() → Geometry                              │
│   └── SetGeometry(geom)                                        │
│                                                             │
│  Geometry                                                      │
│   ├── Clone()                                                  │
│   ├── Transform(coordTransformation)                           │
│   ├── ExportToWkt(String[])                                    │
│   ├── getEnvelope(double[4])                                   │
│   └── Buffer(distance, outGeom)                                │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                  空间参考侧(OSR)                             │
│                                                             │
│  SpatialReference                                                │
│   ├── new SpatialReference(wkt)                                  │
│   ├── ImportFromEPSG(int code)                                    │
│   ├── ImportFromWkt(String[])                                     │
│   ├── ExportToWkt(String[])                                       │
│   ├── ExportToProj4(String[])                                     │
│   ├── SetWellKnownGeogCS("WGS84")                                 │
│   ├── SetUTM(zone, north)                                         │
│   └── IsSame(otherSRS)                                            │
│                                                             │
│  CoordinateTransformation                                        │
│   ├── CreateCoordinateTransformation(srcSRS, dstSRS)               │
│   ├── TransformPoint(double[] xyz)  // in-place, xyz[0..2]        │
│   └── TransformPoints(double[] pts, int n)                        │
└─────────────────────────────────────────────────────────────┘

2.2 关键设计要点

要点说明
带号从 1 开始GetRasterBand(1) 是第一个波段,不是 0,这是 GDAL 历史遗留约定,极易出错
显式资源释放SWIG 生成的对象调用 .delete() 释放原生内存;也可依赖 GC 最终回收,但高并发下建议显式释放
错误码模式多数方法返回 int 错误码(CE_None = 0 表示成功),而非抛异常;可调用 gdal.UseExceptions() 切换为异常模式
数组传递许多 C 风格 API 在 Java 中以 double[]/int[]/String[] 作为输出参数(in-out 语义),需预分配长度
Vector 容器SWIG 将 C++ std::vector<T> 映射为 java.util.Vector<T>(非 ArrayList),遍历时用 Enumeration

3. 环境搭建与依赖管理

3.1 Maven 方式(推荐)

GDAL Java 绑定发布在 Maven Central,groupId 为 org.gdal:

xml
<dependency>
    <groupId>org.gdal</groupId>
    <artifactId>gdal</artifactId>
    <version>3.8.0</version>
</dependency>

注意:Maven Central 上的 gdal.jar 不包含本地 .so/.dll。JAR 只含 Java 类,本地库仍需单独部署到系统的动态库搜索路径。

3.2 本地库部署

Linux

bash
# 方式一:设置环境变量(当前会话有效)
export LD_LIBRARY_PATH=/opt/gdal/lib:$LD_LIBRARY_PATH

# 方式二:永久生效(写入 /etc/ld.so.conf.d/gdal.conf)
echo "/opt/gdal/lib" > /etc/ld.so.conf.d/gdal.conf
sudo ldconfig

macOS

bash
export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH
# Homebrew 安装的 GDAL 若未启用 Java 绑定,需源码编译并指定 --with-java

Windows

bat
set PATH=C:\gdal\bin;C:\gdal\java;%PATH%
:: gdalalljni.dll 放在 bin 目录或 java 目录均可

Windows 预编译包推荐来源:

3.3 从源码构建

bash
# 前置:JDK 7+、SWIG、Ant(必须在 PATH 中)、CMake
cmake -DBUILD_JAVA_BINDINGS=ON \
      -DCMAKE_INSTALL_PREFIX=/opt/gdal \
      ..

make -j$(nproc)
sudo make install
# 产物:
#   /opt/gdal/share/java/gdal.jar
#   /opt/gdal/lib/libgdalalljni.so  (GDAL >= 3.8)

关键 CMake 选项:

选项默认值说明
BUILD_JAVA_BINDINGSON(检测到 JDK 时)是否构建 Java 绑定
GDAL_JAVA_INSTALL_DIR${DATADIR}/javagdal.jar 安装目录
GDAL_JAVA_JNI_INSTALL_DIR(3.8+)JNI 本地库安装目录

3.4 验证安装

bash
# Linux 快速验证(从源码构建目录运行)
export LD_LIBRARY_PATH=$PWD:$PWD/swig/java:$LD_LIBRARY_PATH
java -classpath $PWD/swig/java/gdal.jar:$PWD/swig/java/build/apps gdalinfo test.tif

# Windows
set PATH=%CD%;%CD%\swig\java;%PATH%
java -classpath %CD%\swig\java\gdal.jar;%CD%\swig\java\build\apps gdalinfo test.tif

或写一个最小 Java 测试类:

java
import org.gdal.gdal.gdal;

public class TestGdal {
    public static void main(String[] args) {
        gdal.AllRegister();
        System.out.println("GDAL Version: " + gdal.VersionInfo(""));
        // 期望输出: GDAL 3.x.x, released YYYY/MM/DD
    }
}

4. 栅格数据 API(Raster)

4.1 初始化与打开数据集

java
import org.gdal.gdal.gdal;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.Band;
import org.gdal.gdalconst.gdalconstConstants;

// ① 注册所有驱动(每次 JVM 启动只需调用一次)
gdal.AllRegister();

// 可选:开启异常模式(替代错误码检查)
gdal.UseExceptions();

// ② 打开数据集
Dataset ds = gdal.Open("/path/to/image.tif", gdalconstConstants.GA_ReadOnly);
if (ds == null) {
    throw new RuntimeException("无法打开数据集: " + gdal.GetLastErrorMsg());
}

打开标志(AccessFlag):

常量值含义
GA_ReadOnly0只读
GA_Update1读写
GA_Extended2扩展模式(某些驱动需要)

4.2 读取基本信息

java
// 尺寸
int width  = ds.getRasterXSize();
int height = ds.getRasterYSize();
int bands  = ds.getRasterCount();

// 投影(WKT 字符串)
String wkt = ds.GetProjectionRef();

// 仿射变换参数(GeoTransform)
// [originX, pixelWidth, rowRotation, originY, colRotation, pixelHeight]
double[] gt = new double[6];
ds.GetGeoTransform(gt);

// 像素中心的世界坐标
double worldX = gt[0] + gt[1] * col + gt[2] * row;
double worldY = gt[3] + gt[4] * col + gt[5] * row;

// 元数据
java.util.Vector<String> metadata = ds.GetMetadata_List("");

4.3 波段读取(核心)

java
Band band = ds.GetRasterBand(1);  // 注意:从 1 开始!

// --- 方式 A:整幅读取(适合小图或内存充足场景)---
int xSize = ds.getRasterXSize();
int ySize = ds.getRasterYSize();
byte[] buf = new byte[xSize * ySize];
band.ReadRaster(0, 0, xSize, ySize, buf, xSize, ySize,
                band.getDataType(), 0, 0);

// --- 方式 B:分块读取(推荐用于大图,避免 OOM)---
int[] blockSize = new int[2];
band.GetBlockSize(blockSize);
int blockW = blockSize[0], blockH = blockSize[1];

for (int by = 0; by < ySize; by += blockH) {
    for (int bx = 0; bx < xSize; bx += blockW) {
        int rw = Math.min(blockW, xSize - bx);
        int rh = Math.min(blockH, ySize - by);
        byte[] blockBuf = new byte[rw * rh];
        band.ReadRaster(bx, by, rw, rh, blockBuf, rw, rh,
                        band.getDataType(), 0, 0);
        // 处理 blockBuf ...
    }
}

// --- 方式 C:ReadBlock(直接按物理块读取,最快)---
byte[] blockData = new byte[blockW * blockH];
band.ReadBlock(0, 0, blockData);  // xOff, yOff 以块为单位

4.4 波段写入与创建新数据集

java
// 创建新的 GeoTIFF
org.gdal.gdal.Driver driver = gdal.GetDriverByName("GTiff");
Dataset newDs = driver.Create("output.tif", 512, 512, 3,
                              gdalconstConstants.GDT_Byte, null);

// 设置地理变换与投影
newDs.SetGeoTransform(new double[]{444720, 30, 0, 3751320, 0, -30});
newDs.SetProjection("PROJCS[\"NAD27 / UTM zone 11N\", ...]");  // WKT

// 写入波段数据
Band outBand = newDs.GetRasterBand(1);
outBand.SetDescription("Red");
byte[] rasterData = generateRasterData(512, 512);  // 自行填充
outBand.WriteRaster(0, 0, 512, 512, rasterData, 512, 512,
                    gdalconstConstants.GDT_Byte, 0, 0);

// 设置 NoData
outBand.SetNoDataValue(0.0);

// 刷新缓存并关闭
newDs.FlushCache();
newDs.delete();

数据类型常量对照:

常量C 类型字节数说明
GDT_Byteuint81无符号 8 位
GDT_UInt16uint162无符号 16 位
GDT_Int16int162有符号 16 位
GDT_UInt32uint324无符号 32 位
GDT_Int32int324有符号 32 位
GDT_Float32float4单精度浮点
GDT_Float64double8双精度浮点
GDT_CInt16c-int164复数(实部 int16)
GDT_CFloat32c-float328复数(实部 float32)

4.5 统计信息与质量评估

java
// 精确统计(全图扫描,较慢)
double[] min = {0}, max = {0}, mean = {0}, stdDev = {0};
int err = band.GetStatistics(false, false, min, max, mean, stdDev);
if (err == gdalconstConstants.CE_None) {
    System.out.printf("Min=%.2f Max=%.2f Mean=%.2f StdDev=%.2f%n",
            min[0], max[0], mean[0], stdDev[0]);
}

// 近似统计(采样,快得多)
band.GetStatistics(true, true, min, max, mean, stdDev);

// 计算精确 Min/Max(不依赖文件头信息)
double[] computedMM = new double[2];
band.ComputeRasterMinMax(computedMM, 0);

// 直方图
double[] histMin = {min[0]}, histMax = {max[0]};
int[][] histogram = new int[1][];
band.GetDefaultHistogram(histMin[0], histMax[0], histogram, true,
                         new org.gdal.gdal.TermProgressCallback());

// Checksum(用于校验)
int checksum = band.Checksum();

4.6 掩膜带(Alpha / NoData Mask)

java
int maskFlags = band.GetMaskFlags();
// GMF_NODATA     = 1  存在 NoData 值定义
// GMF_ALL_VALID  = 2  所有像素均有效
// GMF_PER_DATASET= 4  掩膜适用于整个数据集
// GMF_ALPHA      = 8  Alpha 通道

if ((maskFlags & (gdalconstConstants.GMF_NODATA | gdalconstConstants.GMF_ALL_VALID)) == 0) {
    Band maskBand = band.GetMaskBand();
    // maskBand 中 0 = 无效,255 = 有效
    byte[] maskData = new byte[width * height];
    maskBand.ReadRaster(0, 0, width, height, maskData, width, height,
                        gdalconstConstants.GDT_Byte, 0, 0);
}

4.7 概览(Overviews / Pyramids)

java
int overviewCount = band.GetOverviewCount();
for (int i = 0; i < overviewCount; i++) {
    Band ov = band.GetOverview(i);
    System.out.println("Overview " + i + ": " + ov.getXSize() + "x" + ov.getYSize());
}

// 生成概览(加速大尺度显示)
// 在 Python 中对应 gdaladdo;Java 中可通过 Dataset.BuildOverviews 调用
ds.BuildOverviews("Average", 1, new int[]{2, 4, 8, 16}, 0, null, null);

4.8 颜色表与调色板

java
org.gdal.gdal.ColorTable ct = band.GetRasterColorTable();
if (ct != null) {
    int count = ct.GetCount();
    for (int i = 0; i < Math.min(count, 10); i++) {
        org.gdal.gdal.ColorEntry entry = ct.GetColorEntry(i);
        System.out.printf("%d: R=%d G=%d B=%d A=%d%n",
                i, entry.getC1(), entry.getC2(), entry.getC3(), entry.getC4());
    }
}

5. 矢量数据 API(OGR Vector)

5.1 初始化与打开数据源

java
import org.gdal.ogr.ogr;
import org.gdal.ogr.DataSource;
import org.gdal.ogr.Layer;
import org.gdal.ogr.Feature;
import org.gdal.ogr.Geometry;
import org.gdal.ogr.FieldDefn;
import org.gdal.ogr.FeatureDefn;

// 注册所有矢量驱动
ogr.RegisterAll();

// 打开数据源(支持 shp、geojson、gpkg、sqlite、kml 等)
DataSource ds = ogr.GetDriverByName("ESRI Shapefile").Open("data.shp", 0);
// 或直接让 GDAL 自动探测:
DataSource dsAuto = ogr.Open("data.shp", 0);

5.2 遍历图层与要素

java
int layerCount = ds.GetLayerCount();
for (int li = 0; li < layerCount; li++) {
    Layer layer = ds.GetLayer(li);
    String layerName = layer.GetName();
    System.out.println("Layer: " + layerName);

    // 几何类型
    System.out.println("Geom Type: " +
        org.gdal.ogr.ogr.GetGeometryTypeName(layer.GetGeomType()));

    // 字段定义
    FeatureDefn defn = layer.GetLayerDefn();
    int fieldCount = defn.GetFieldCount();
    for (int fi = 0; fi < fieldCount; fi++) {
        FieldDefn fdefn = defn.GetFieldDefn(fi);
        System.out.printf("  Field[%d]: %s (%s)%n", fi,
            fdefn.GetName(),
            org.gdal.ogr.ogr.GetFieldTypeName(fdefn.GetType()));
    }

    // 遍历要素
    layer.ResetReading();
    Feature feature;
    while ((feature = layer.GetNextFeature()) != null) {
        // 属性
        String name = feature.GetFieldAsString("NAME");
        double value = feature.GetFieldAsDouble("VALUE");

        // 几何
        Geometry geom = feature.GetGeometryRef();
        if (geom != null) {
            String[] wkt = new String[1];
            geom.ExportToWkt(wkt);
            System.out.println("  " + name + " -> " + wkt[0]);
        }
        feature.delete();  // 释放原生内存
    }
}
ds.delete();

5.3 空间过滤与 SQL 查询

java
// 空间过滤:只读取与给定几何相交的要素
Layer layer = ds.GetLayer(0);

Geometry filterGeom = new Geometry();
filterGeom.fromWkt("POLYGON((116.0 39.0, 117.0 39.0, 117.0 40.0, 116.0 40.0, 116.0 39.0))");
layer.SetSpatialFilter(filterGeom);

long count = layer.GetFeatureCount();
System.out.println("Matching features: " + count);

// 属性 SQL 查询(OGR SQL 方言)
Layer sqlLayer = ds.ExecuteSQL(
    "SELECT * FROM my_layer WHERE population > 1000000 ORDER BY name",
    null,   // spatialFilter
    "OGRSQL" // dialect
);
// ... 处理结果 ...
ds.ReleaseDS(sqlLayer);  // 必须释放 SQL 结果图层

支持的 SQL 方言:OGRSQL(通用)、SQLITE、GEOS、MYSQL、POSTGRES、MSSQL 等,取决于底层驱动能力。

5.4 创建矢量数据

java
// 创建 GeoJSON 文件
org.gdal.ogr.Driver geoJsonDriver = ogr.GetDriverByName("GeoJSON");
DataSource outDs = geoJsonDriver.CreateDataSource("output.geojson", null);

// 创建图层
SpatialReference srs = new SpatialReference();
srs.ImportFromEPSG(4326);  // WGS84

Layer outLayer = outDs.CreateLayer("points", srs,
    org.gdal.ogr.wkbConstants.wkbPoint, null);

// 添加字段
outLayer.CreateField(new FieldDefn("name", org.gdal.ogr.ogr.OFTString, 64, 0));
outLayer.CreateField(new FieldDefn("elevation", org.gdal.ogr.ogr.OFTReal, 0, 2));

// 创建要素
Feature feat = new Feature(outLayer.GetLayerDefn());
feat.SetField("name", "北京");
feat.SetField("elevation", 43.5);

Geometry point = new Geometry();
point.AssignPoint(116.4074, 39.9042, 0);
feat.SetGeometry(point);

outLayer.CreateFeature(feat);
feat.delete();
point.delete();

outDs.delete();

5.5 几何操作

java
Geometry geom = feature.GetGeometryRef();

// 边界框
double[] env = new double[4];
geom.getEnvelope(env);  // [minX, maxX, minY, maxY]

// 缓冲区
Geometry bufferGeom = new Geometry();
geom.Buffer(0.001, bufferGeom, 0);  // ~100m @ equator

// 面积 / 长度
double area = geom.Area();       // 仅对 Polygon 有意义
double length = geom.Length();   // 仅对 LineString 有意义

// 克隆(深拷贝)
Geometry copy = geom.Clone();

// 几何求交(需要 GEOS 支持)
Geometry other = ...;
Geometry intersection = new Geometry();
// 通过 ogr 的几何运算 API 或 OGR SQL 的 INTERSECTS 谓词

5.6 常见矢量格式驱动名

驱动短名格式
ESRI ShapefileESRI Shapefile (.shp)
GeoJSONGeoJSON
GPKGGeoPackage
SQLiteSQLite (+ SpatiaLite)
Memory内存临时层
KML / LIBKMLKML / KMZ
CSVCSV(无几何或 WKT 列)
FlatGeobufFlatGeobuf
GMLGML 2/3
DXFAutoCAD DXF
MapInfo FileMapInfo TAB
PostgreSQLPostGIS
MySQLMySQL Spatial

6. 空间参考与坐标变换(OSR)

6.1 SpatialReference 基本操作

java
import org.gdal.osr.SpatialReference;

// 从 EPSG 代码创建
SpatialReference srs = new SpatialReference();
srs.ImportFromEPSG(4326);  // WGS84 经纬度

// 从 WKT 字符串导入
SpatialReference srsFromWkt = new SpatialReference();
srsFromWkt.ImportFromWkt(new String[]{wktString});

// 从 Proj4 字符串导入
srs.ImportFromProj4("+proj=utm +zone=50 +datum=WGS84 +units=m +no_defs");

// 导出为各种格式
String[] wktOut = new String[1];
srs.ExportToWkt(wktOut);

String[] proj4Out = new String[1];
srs.ExportToProj4(proj4Out);

String[] prettyWktOut = new String[1];
srs.ExportToPrettyWkt(prettyWktOut, 0);

// 判断两个 SRS 是否相同
boolean same = srs.IsSame(otherSrs);

// 获取地理坐标系(如投影坐标系转回 WGS84)
SpatialReference geogCS = srs.CloneGeogCS();

6.2 坐标变换

java
import org.gdal.osr.CoordinateTransformation;

// 源:EPSG:4326 (WGS84)
SpatialReference srcSrs = new SpatialReference();
srcSrs.ImportFromEPSG(4326);

// 目标:EPSG:3857 (Web Mercator)
SpatialReference dstSrs = new SpatialReference();
dstSrs.ImportFromEPSG(3857);

// 创建变换器(开销较大,建议复用)
CoordinateTransformation ct =
    CoordinateTransformation.CreateCoordinateTransformation(srcSrs, dstSrs);

// 单点变换(in-place,xyz[0]=x, xyz[1]=y, xyz[2]=z)
double[] point = {116.4074, 39.9042, 0};
ct.TransformPoint(point);
System.out.printf("Mercator: %.2f, %.2f%n", point[0], point[1]);
// 输出: Mercator: 12958175.00, 4852833.00

// 批量变换(更高效)
double[] points = {
    116.4074, 39.9042, 0,   // 北京
    121.4737, 31.2304, 0,   // 上海
    113.2644, 23.1291, 0    // 广州
};
ct.TransformPoints(points, 3);

// 清理
ct.delete();
srcSrs.delete();
dstSrs.delete();

6.3 栅格重投影(配合算法库)

GDAL Java 绑定中重投影通常通过 gdal.AutoCreateWarpedVRT 或 gdal.Warp 完成:

java
import org.gdal.gdal.gdal;

// 方式一:创建虚拟重投影 VRT(内存中,不落盘)
Dataset warpedVrt = gdal.AutoCreateWarpedVRT(
    srcDataset,           // 源数据集
    null,                 // srcGeoTransform(null 则用数据集自带的)
    srcSrsWkt,            // 源 SRS WKT
    dstSrsWkt,            // 目标 SRS WKT
    gdalconstConstants.GCubic,  // 重采样算法
    0,                    // 容差
    null                  // warp options
);
// 然后像普通 Dataset 一样读取 warpedVrt

// 方式二:直接 Warp 到文件
gdal.Warp("output_reprojected.tif", "input.tif",
    gdalconstConstants.GCubic,
    new String[]{"-t_srs", "EPSG:3857"});

重采样算法常量:

常量说明适用场景
GNear最近邻分类数据、整数栅格
GBilinear双线性连续数据(默认推荐)
GCubic三次卷积高质量连续数据
GCubicSpline三次样条平滑插值
GLanczosLanczos最高质量(慢)
GAverage平均降采样聚合
GRMS均方根降采样保留方差

7. 常用工具类与回调机制

7.1 进度回调

java
import org.gdal.gdal.ProgressCallback;
import org.gdal.gdal.TermProgressCallback;

// 终端进度条(内置实现)
TermProgressCallback progress = new TermProgressCallback();

// 自定义进度回调
ProgressCallback myProgress = new ProgressCallback() {
    @Override
    public int run(double fraction, String message, void* data) {
        // fraction: 0.0 ~ 1.0
        System.out.printf("\rProgress: %.1f%% %s", fraction * 100, message);
        return 1;  // 返回 1 继续,0 取消
    }
};

7.2 全局配置项(Configuration Options)

java
// 等价于设置环境变量,优先级高于实际环境变量
gdal.SetConfigOption("GDAL_CACHEMAX", "512");           // 缓存大小(MB)
gdal.SetConfigOption("GDAL_NUM_THREADS", "4");          // 线程数
gdal.SetConfigOption("CPL_VSIL_CURL_TIMEOUT", "30");    // HTTP 超时(秒)
gdal.SetConfigOption("OGR_SQLITE_SPATIALITE", "YES");   // 启用 SpatiaLite

// 读取当前值
String cacheMax = gdal.GetConfigOption("GDAL_CACHEMAX");

// 清除
gdal.SetConfigOption("GDAL_CACHEMAX", null);

常用配置项:

配置项作用
GDAL_CACHEMAX内存缓存上限(MB),默认约 25% 可用 RAM
GDAL_NUM_THREADS并行读写的线程数
GDAL_USE_RRD是否使用 RRD 概览(旧版 TIFF)
CPL_DEBUG设为 ON 开启调试日志
CPL_LOG日志文件路径
OGR_GEOMETRY_TYPES限制允许的几何类型
SHAPE_ENCODINGShapefile 编码(UTF-8 / GBK 等)

7.3 日志与错误处理

java
// 错误码常量
gdalconstConstants.CE_None      // 0 - 成功
gdalconstConstants.CPLError     // 1 - 一般错误
gdalconstConstants.CE_Failure   // 2 - 严重错误
gdalconstConstants.CE_Fatal     // 3 - 致命错误

// 获取最后错误
int lastErrNo = gdal.GetLastErrorNo();
String lastErrMsg = gdal.GetLastErrorMsg();

// 异常模式(推荐)
gdal.UseExceptions();
try {
    Dataset ds = gdal.Open("nonexistent.tif", 0);
} catch (Exception e) {
    System.err.println("打开失败: " + e.getMessage());
}

// 调试日志
gdal.SetConfigOption("CPL_DEBUG", "ON");
gdal.SetConfigOption("CPL_LOG", "/tmp/gdal_debug.log");

7.4 VSI 虚拟文件系统

GDAL 的 VSI(Virtual SI)层允许透明访问远程与压缩文件,在 Java 中直接使用路径前缀即可:

java
// 普通文件
gdal.Open("/local/path/file.tif", 0);

// ZIP 内文件(无需解压)
gdal.Open("/vsizip/archive.zip!subdir/file.tif", 0);

// GZIP
gdal.Open("/vsigzip/data.tif.gz", 0);

// HTTP(S)
gdal.Open("/vsicurl/https://example.com/raster.tif", 0);

// S3(需要 AWS 凭证环境变量)
gdal.Open("/vsis3/bucket-name/path/file.tif", 0);

// 内存缓冲
Dataset memDs = gdal.GetDriverByName("MEM")
    .Create("", width, height, 1, gdalconstConstants.GDT_Byte, null);

8. 完整示例程序

8.1 栅格信息查看器(类似 gdalinfo)

java
import org.gdal.gdal.*;
import org.gdal.gdalconst.gdalconstConstants;
import org.gdal.osr.SpatialReference;
import org.gdal.osr.CoordinateTransformation;
import java.util.Enumeration;
import java.util.Vector;

/**
 * 栅格信息查看器 —— Java 版 gdalinfo
 */
public class RasterInfoViewer {

    public static void main(String[] args) {
        if (args.length < 1) {
            System.err.println("Usage: java RasterInfoViewer <raster-file>");
            System.exit(1);
        }

        gdal.AllRegister();
        gdal.UseExceptions();

        try {
            Dataset ds = gdal.Open(args[0], gdalconstConstants.GA_ReadOnly);
            printDatasetInfo(ds);
            ds.delete();
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            System.exit(1);
        } finally {
            gdal.GDALDestroyDriverManager();
        }
    }

    private static void printDatasetInfo(Dataset ds) {
        Driver driver = ds.GetDriver();
        System.out.println("Driver: " + driver.getShortName()
                         + " / " + driver.getLongName());

        int w = ds.getRasterXSize();
        int h = ds.getRasterYSize();
        int b = ds.getRasterCount();
        System.out.printf("Size: %d x %d (bands: %d)%n", w, h, b);

        // 投影
        String projWkt = ds.GetProjectionRef();
        if (projWkt != null && !projWkt.isEmpty()) {
            SpatialReference srs = new SpatialReference(projWkt);
            String[] pretty = new String[1];
            srs.ExportToPrettyWkt(pretty, 0);
            System.out.println("Coordinate System:\n" + pretty[0]);
            srs.delete();
        }

        // GeoTransform
        double[] gt = new double[6];
        ds.GetGeoTransform(gt);
        if (gt[2] == 0 && gt[4] == 0) {
            System.out.printf("Origin: (%.6f, %.6f)%n", gt[0], gt[3]);
            System.out.printf("Pixel Size: (%.6f, %.6f)%n", gt[1], gt[5]);
        } else {
            System.out.println("GeoTransform:");
            System.out.printf("  %.6f, %.6f, %.6f%n", gt[0], gt[1], gt[2]);
            System.out.printf("  %.6f, %.6f, %.6f%n", gt[3], gt[4], gt[5]);
        }

        // 角点坐标(经纬度)
        printCorner(ds, "Upper Left ", 0, 0);
        printCorner(ds, "Lower Left ", 0, h);
        printCorner(ds, "Upper Right", w, 0);
        printCorner(ds, "Lower Right", w, h);
        printCorner(ds, "Center     ", w / 2.0, h / 2.0);

        // 波段详情
        for (int i = 0; i < b; i++) {
            Band band = ds.GetRasterBand(i + 1);
            int[] bs = new int[2];
            band.GetBlockSize(bs);

            Double[] min = {null}, max = {null};
            band.GetMinimum(min);
            band.GetMaximum(max);

            System.out.printf("Band %d:%n", i + 1);
            System.out.printf("  Block: %dx%d%n", bs[0], bs[1]);
            System.out.printf("  Type: %s%n", gdal.GetDataTypeName(band.getDataType()));
            System.out.printf("  ColorInterp: %s%n",
                gdal.GetColorInterpretationName(band.GetRasterColorInterpretation()));

            if (min[0] != null || max[0] != null) {
                System.out.printf("  Min: %s, Max: %s%n", min[0], max[0]);
            }

            Double[] noData = {null};
            band.GetNoDataValue(noData);
            if (noData[0] != null) {
                System.out.printf("  NoData: %s%n", noData[0]);
            }

            if (band.GetOverviewCount() > 0) {
                System.out.print("  Overviews: ");
                for (int oi = 0; oi < band.GetOverviewCount(); oi++) {
                    Band ov = band.GetOverview(oi);
                    if (oi > 0) System.out.print(", ");
                    System.out.print(ov.getXSize() + "x" + ov.getYSize());
                }
                System.out.println();
            }
        }
    }

    private static void printCorner(Dataset ds, String label, double px, double py) {
        double[] gt = new double[6];
        ds.GetGeoTransform(gt);
        double geoX = gt[0] + gt[1] * px + gt[2] * py;
        double geoY = gt[3] + gt[4] * px + gt[5] * py;

        System.out.print(label + "(" + geoX + ", " + geoY + ")");

        String projWkt = ds.GetProjectionRef();
        if (projWkt != null && !projWkt.isEmpty()) {
            SpatialReference srcSrs = new SpatialReference(projWkt);
            SpatialReference latLong = srcSrs.CloneGeogCS();
            if (latLong != null) {
                CoordinateTransformation ct =
                    CoordinateTransformation.CreateCoordinateTransformation(srcSrs, latLong);
                if (ct != null) {
                    double[] pt = {geoX, geoY, 0};
                    ct.TransformPoint(pt);
                    System.out.printf(" (%s, %s)",
                        gdal.DecToDMS(pt[0], "Long", 2),
                        gdal.DecToDMS(pt[1], "Lat", 2));
                    ct.delete();
                }
            }
            if (latLong != null) latLong.delete();
            srcSrs.delete();
        }
        System.out.println();
    }
}

8.2 矢量数据格式转换器(类似 ogr2ogr 简化版)

java
import org.gdal.gdal.gdal;
import org.gdal.ogr.*;
import org.gdal.ogr.ogrConstants;
import org.gdal.osr.SpatialReference;
import org.gdal.osr.CoordinateTransformation;

/**
 * 简化版矢量格式转换器
 * 用法: java VectorConverter <src-file> <dst-file> [dst-srs-wkt-or-epsg]
 */
public class VectorConverter {

    public static void main(String[] args) {
        if (args.length < 2) {
            System.err.println("Usage: java VectorConverter <src> <dst> [dst-srs]");
            System.exit(1);
        }

        gdal.AllRegister();
        ogr.RegisterAll();
        gdal.UseExceptions();

        String srcFile = args[0];
        String dstFile = args[1];
        String dstSrsStr = args.length > 2 ? args[2] : null;

        try {
            // 打开源
            DataSource srcDs = ogr.Open(srcFile, 0);
            if (srcDs == null) {
                throw new RuntimeException("无法打开源文件: " + srcFile);
            }

            // 确定目标驱动
            String dstDriverName = guessDriver(dstFile);
            org.gdal.ogr.Driver dstDriver = ogr.GetDriverByName(dstDriverName);
            if (dstDriver == null) {
                throw new RuntimeException("未知的目标格式: " + dstDriverName);
            }

            // 创建目标数据源
            DataSource dstDs = dstDriver.CreateDataSource(dstFile, null);

            SpatialReference dstSrs = null;
            if (dstSrsStr != null) {
                dstSrs = new SpatialReference();
                if (dstSrsStr.matches("\\d+")) {
                    dstSrs.ImportFromEPSG(Integer.parseInt(dstSrsStr));
                } else {
                    dstSrs.ImportFromWkt(new String[]{dstSrsStr});
                }
            }

            long totalFeatures = 0;
            for (int li = 0; li < srcDs.GetLayerCount(); li++) {
                Layer srcLayer = srcDs.GetLayer(li);
                String layerName = srcLayer.GetName();

                // 创建对应目标图层
                SpatialReference layerSrs = srcLayer.GetSpatialRef();
                SpatialReference outSrs = dstSrs != null ? dstSrs : layerSrs;

                Layer dstLayer = dstDs.CreateLayer(
                    layerName, outSrs, srcLayer.GetGeomType(), null);

                // 复制字段
                FeatureDefn srcDefn = srcLayer.GetLayerDefn();
                for (int fi = 0; fi < srcDefn.GetFieldCount(); fi++) {
                    FieldDefn srcField = srcDefn.GetFieldDefn(fi);
                    dstLayer.CreateField(new FieldDefn(
                        srcField.GetName(),
                        srcField.GetType(),
                        srcField.GetWidth(),
                        srcField.GetPrecision()));
                }

                // 准备坐标变换
                CoordinateTransformation ct = null;
                if (dstSrs != null && layerSrs != null
                        && !layerSrs.IsSame(dstSrs)) {
                    ct = CoordinateTransformation.CreateCoordinateTransformation(
                        layerSrs, dstSrs);
                }

                // 逐要素转换
                srcLayer.ResetReading();
                Feature srcFeat;
                long count = 0;
                while ((srcFeat = srcLayer.GetNextFeature()) != null) {
                    Feature dstFeat = new Feature(dstLayer.GetLayerDefn());

                    // 复制属性
                    for (int fi = 0; fi < srcDefn.GetFieldCount(); fi++) {
                        String fieldName = srcDefn.GetFieldDefn(fi).GetName();
                        switch (srcDefn.GetFieldDefn(fi).GetType()) {
                            case ogr.OFTString:
                                dstFeat.SetField(fieldName,
                                    srcFeat.GetFieldAsString(fieldName));
                                break;
                            case ogr.OFTInteger:
                                dstFeat.SetField(fieldName,
                                    srcFeat.GetFieldAsInteger(fieldName));
                                break;
                            case ogr.OFTReal:
                                dstFeat.SetField(fieldName,
                                    srcFeat.GetFieldAsDouble(fieldName));
                                break;
                            default:
                                dstFeat.SetField(fieldName,
                                    srcFeat.GetFieldAsString(fieldName));
                        }
                    }

                    // 复制并变换几何
                    Geometry srcGeom = srcFeat.GetGeometryRef();
                    if (srcGeom != null) {
                        Geometry dstGeom = srcGeom.Clone();
                        if (ct != null) {
                            dstGeom.Transform(ct);
                        }
                        dstFeat.SetGeometryDirectly(dstGeom);
                    }

                    dstLayer.CreateFeature(dstFeat);
                    dstFeat.delete();
                    srcFeat.delete();
                    count++;
                }

                System.out.printf("Layer '%s': %d features converted%n",
                    layerName, count);
                totalFeatures += count;

                if (ct != null) ct.delete();
                if (layerSrs != null && dstSrs == null) layerSrs.delete();
            }

            System.out.println("Total: " + totalFeatures + " features");
            dstDs.delete();
            srcDs.delete();
            if (dstSrs != null) dstSrs.delete();

        } catch (Exception e) {
            System.err.println("Conversion failed: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }

    private static String guessDriver(String filename) {
        String lower = filename.toLowerCase();
        if (lower.endsWith(".shp")) return "ESRI Shapefile";
        if (lower.endsWith(".geojson") || lower.endsWith(".json")) return "GeoJSON";
        if (lower.endsWith(".gpkg")) return "GPKG";
        if (lower.endsWith(".kml")) return "KML";
        if (lower.endsWith(".kmz")) return "LIBKML";
        if (lower.endsWith(".csv")) return "CSV";
        if (lower.endsWith(".fgb")) return "FlatGeobuf";
        if (lower.endsWith(".tab")) return "MapInfo File";
        if (lower.endsWith(".dxf")) return "DXF";
        throw new RuntimeException("无法推断目标格式: " + filename);
    }
}

8.3 坐标批量转换工具

java
import org.gdal.gdal.gdal;
import org.gdal.osr.SpatialReference;
import org.gdal.osr.CoordinateTransformation;

/**
 * 批量坐标转换:WGS84 → 指定目标坐标系
 */
public class BatchReproject {

    public static void main(String[] args) {
        gdal.AllRegister();

        // 源:WGS84
        SpatialReference srcSrs = new SpatialReference();
        srcSrs.ImportFromEPSG(4326);

        // 目标:CGCS2000 / 3-degree Gauss-Kruger CM 114E(示例)
        // 这里用 EPSG:4547 (CGCS2000 / 3-degree GK CM 114E)
        SpatialReference dstSrs = new SpatialReference();
        dstSrs.ImportFromEPSG(4547);

        CoordinateTransformation ct =
            CoordinateTransformation.CreateCoordinateTransformation(srcSrs, dstSrs);

        // 中国主要城市 WGS84 坐标
        String[][] cities = {
            {"北京", "116.4074", "39.9042"},
            {"上海", "121.4737", "31.2304"},
            {"广州", "113.2644", "23.1291"},
            {"成都", "104.0665", "30.5723"},
            {"乌鲁木齐", "87.6168", "43.8256"},
        };

        System.out.printf("%-10s %-12s %-12s %-15s %-15s%n",
            "城市", "Lon(WGS84)", "Lat(WGS84)", "X(目标)", "Y(目标)");
        System.out.println("-".repeat(64));

        for (String[] city : cities) {
            double[] pt = {
                Double.parseDouble(city[1]),
                Double.parseDouble(city[2]),
                0
            };
            int result = ct.TransformPoint(pt);
            if (result == 0) {
                System.out.printf("%-10s %-12.6f %-12.6f %-15.2f %-15.2f%n",
                    city[0], city[1], city[2], pt[0], pt[1]);
            } else {
                System.out.printf("%-10s FAILED%n", city[0]);
            }
        }

        ct.delete();
        srcSrs.delete();
        dstSrs.delete();
    }
}

8.4 栅格裁剪与子集读取

java
import org.gdal.gdal.*;
import org.gdal.gdalconst.gdalconstConstants;

/**
 * 按地理范围裁剪栅格
 */
public class RasterClipper {

    public static void main(String[] args) throws Exception {
        gdal.AllRegister();
        gdal.UseExceptions();

        String srcFile = "large_raster.tif";
        String dstFile = "clipped_output.tif";

        // 裁剪范围(世界坐标)
        double clipMinX = 444720.0;
        double clipMinY = 3751000.0;
        double clipMaxX = 445500.0;
        double clipMaxY = 3751500.0;

        Dataset srcDs = gdal.Open(srcFile, gdalconstConstants.GA_ReadOnly);

        double[] gt = new double[6];
        srcDs.GetGeoTransform(gt);

        // 计算像素范围
        int xStart = (int) Math.floor((clipMinX - gt[0]) / gt[1]);
        int yStart = (int) Math.floor((gt[3] - clipMaxY) / gt[5]);  // 注意 Y 轴方向
        int xEnd   = (int) Math.ceil ((clipMaxX - gt[0]) / gt[1]);
        int yEnd   = (int) Math.ceil ((gt[3] - clipMinY) / gt[5]);

        // 边界保护
        xStart = Math.max(0, xStart);
        yStart = Math.max(0, yStart);
        xEnd = Math.min(srcDs.getRasterXSize(), xEnd);
        yEnd = Math.min(srcDs.getRasterYSize(), yEnd);

        int clipW = xEnd - xStart;
        int clipH = yEnd - yStart;
        int bandCount = srcDs.getRasterCount();

        System.out.printf("Clipping region: [%d,%d] - [%d,%d] (%dx%d pixels)%n",
            xStart, yStart, xEnd, yEnd, clipW, clipH);

        // 创建输出
        org.gdal.gdal.Driver tiffDriver = gdal.GetDriverByName("GTiff");
        Dataset dstDs = tiffDriver.Create(dstFile, clipW, clipH, bandCount,
            gdalconstConstants.GDT_Float32, null);

        // 设置地理参考(偏移裁剪原点)
        double[] dstGt = new double[6];
        dstGt[0] = gt[0] + gt[1] * xStart;
        dstGt[1] = gt[1];
        dstGt[2] = gt[2];
        dstGt[3] = gt[3] + gt[5] * yStart;
        dstGt[4] = gt[4];
        dstGt[5] = gt[5];
        dstDs.SetGeoTransform(dstGt);
        dstDs.SetProjection(srcDs.GetProjectionRef());

        // 逐波段复制数据
        for (int b = 1; b <= bandCount; b++) {
            Band srcBand = srcDs.GetRasterBand(b);
            Band dstBand = dstDs.GetRasterBand(b);

            float[] buf = new float[clipW * clipH];
            srcBand.ReadRaster(xStart, yStart, clipW, clipH,
                buf, clipW, clipH, gdalconstConstants.GDT_Float32, 0, 0);
            dstBand.WriteRaster(0, 0, clipW, clipH,
                buf, clipW, clipH, gdalconstConstants.GDT_Float32, 0, 0);

            Double[] noData = {null};
            srcBand.GetNoDataValue(noData);
            if (noData[0] != null) {
                dstBand.SetNoDataValue(noData[0]);
            }
        }

        dstDs.FlushCache();
        dstDs.delete();
        srcDs.delete();
        System.out.println("Done: " + dstFile);
    }
}

9. 性能优化与线程安全

9.1 内存管理

策略说明
显式 delete()对长时间持有的 Dataset/Band/Feature 对象,用完立即调用 .delete() 释放原生堆内存,不要依赖 GC
分块读取大图务必使用 ReadBlock 或按 GetBlockSize 分块 ReadRaster,避免一次性加载整个阵列导致 OOM
调整缓存gdal.SetConfigOption("GDAL_CACHEMAX", "1024") 增大内存缓存(单位 MB)
使用 MEM 驱动中间结果放内存数据集,避免反复磁盘 I/O

9.2 多线程注意事项

java
// GDAL 本身是线程安全的(大部分 API),但以下对象不可跨线程共享:
// - Dataset 实例
// - Band 实例
// - Feature 实例
// - CoordinateTransformation 实例

// 正确做法:每个线程打开自己的 Dataset
class Worker implements Runnable {
    private final String filePath;
    Worker(String path) { this.filePath = path; }

    @Override
    public void run() {
        Dataset ds = gdal.Open(filePath, gdalconstConstants.GA_ReadOnly);
        // 在 ds 上操作...
        ds.delete();  // 在线程结束时释放
    }
}

关键:Java GC 在独立线程中运行,而 GDAL 原生对象的生命周期由 C++ 侧管理。若 GDAL 未以多线程模式编译,GC 触发时的原生内存回收可能引发段错误。生产环境务必确认 GDAL 是以 -DWITH_THREADS=YES(或 CMake 默认)编译的。

9.3 I/O 优化

技巧效果
读取前调用 SetCacheFlusher控制缓存刷新时机,减少不必要 flush
使用 /vsicurl/ 配合 HTTP Range 请求云存储按需下载,无需全量拉取
对频繁访问的小区域建立 Overview大幅减少读取的数据量
GDAL_NUM_THREADS 设为 CPU 核数并行块级 I/O
矢量数据用 GetFeatureCount() 预分配集合避免 ArrayList 扩容

9.4 大数据集处理模式

java
// 流式处理模式:边读边处理边丢弃,峰值内存恒定
Dataset ds = gdal.Open(hugeFile, gdalconstConstants.GA_ReadOnly);
int blockW, blockH;
int[] bs = new int[2];
ds.GetRasterBand(1).GetBlockSize(bs);
blockW = bs[0]; blockH = bs[1];

for (int by = 0; by < ds.getRasterYSize(); by += blockH) {
    for (int bx = 0; bx < ds.getRasterXSize(); bx += blockW) {
        int rw = Math.min(blockW, ds.getRasterXSize() - bx);
        int rh = Math.min(blockH, ds.getRasterYSize() - by);
        byte[] block = new byte[rw * rh];
        ds.GetRasterBand(1).ReadRaster(bx, by, rw, rh,
            block, rw, rh, gdalconstConstants.GDT_Byte, 0, 0);
        processBlock(block, bx, by, rw, rh);  // 处理后立即丢弃
    }
}

10. 常见问题与故障排查

10.1 UnsatisfiedLinkError: no gdalalljni in java.library.path

原因:JNI 本地库不在 JVM 搜索路径中。

解决:

bash
# Linux
export LD_LIBRARY_PATH=/path/to/gdal/lib:$LD_LIBRARY_PATH

# 或在 Java 启动时指定
java -Djava.library.path=/path/to/gdal/lib -cp gdal.jar:app.jar MyApp

# Windows
set PATH=C:\gdal\bin;%PATH%

10.2 gdal.jar 与本地库版本不匹配

症状:NoSuchMethodError 或 UnsatisfiedLinkError 中出现方法签名不匹配。

解决:确保 gdal.jar 和 gdalalljni.* 来自同一 GDAL 源代码版本。Maven Central 上的 JAR 版本需与系统安装的 GDAL 主版本一致(如 jar 3.8.0 配 GDAL 3.8.x)。

10.3 Shapefile 中文乱码

原因:Shapefile 的 .dbf 部分通常使用本地编码(中国环境下多为 GBK/GB2312),而 Java 默认 UTF-8。

解决:

java
// 打开前设置编码
gdal.SetConfigOption("SHAPE_ENCODING", "GBK");
// 或
gdal.SetConfigOption("SHAPE_ENCODING", "=GBK");  // 强制指定,不自动检测

10.4 内存泄漏(原生堆持续增长)

原因:SWIG 包装对象未及时 delete(),原生 C++ 对象堆积。

解决:

  • 所有 Dataset、Band、Feature、Geometry、SpatialReference、CoordinateTransformation 对象在使用完毕后显式调用 .delete()
  • 在 finally 块中确保释放
  • 监控 RSS 内存(不仅是 Java 堆),可用 jcmd <pid> VM.native_memory summary 或操作系统工具

10.5 GetRasterBand 返回 null 或越界

原因:带号从 1 开始,传入 0 会返回 null。

java
// 错误
Band b0 = ds.GetRasterBand(0);  // null!

// 正确
Band b1 = ds.GetRasterBand(1);  // 第一个波段

10.6 多线程下随机崩溃(段错误)

原因:GDAL 未以多线程模式编译,GC 线程与主线程竞争原生资源。

解决:重新编译 GDAL 时确保多线程支持开启(CMake 默认开启;autoconf 需加 --with-threads)。

10.7 打开远程文件超时

java
gdal.SetConfigOption("CPL_VSIL_CURL_TIMEOUT", "60");      // 连接超时
gdal.SetConfigOption("CPL_VSIL_CURL_CONNECT_TIMEOUT", "10"); // 建连超时
gdal.SetConfigOption("CPL_VSIL_CURL_RETRY", "3");          // 重试次数

10.8 如何确认某个格式驱动是否可用

java
gdal.AllRegister();
Driver driver = gdal.GetDriverByName("GTiff");
if (driver == null) {
    System.err.println("GTiff 驱动不可用,请检查 GDAL 编译选项");
}

// 列出所有可用栅格驱动
int driverCount = gdal.GetDriverCount();
for (int i = 0; i < driverCount; i++) {
    Driver d = gdal.GetDriver(i);
    System.out.println(d.getShortName() + " - " + d.getLongName());
}

11. 版本演进与兼容性

11.1 关键版本节点

版本年份重要变更
1.7.02008Java 绑定首次随官方发布,Javadoc 开始覆盖
1.11.x2012稳定期,Maven Central 最早收录的版本之一
2.02016OGR 引入 OGRLayer::GetNextByIndex,Java 同步更新
2.42018引入 SetFields 批量属性写入;GeoJSON 驱动增强
3.02019重大重构:ogr 包重组,部分类迁移;废弃 OGRGeometry::transform 旧签名;要求 C++11
3.12020新增 ExecuteSQL 支持更多方言;BuildOverviews 改进
3.42021GDALWarp 选项扩展;/vsis3/ 改进
3.82023JNI 库安装路径分离(GDAL_JAVA_JNI_INSTALL_DIR);AUTODETECT_JSON_STRINGS 等 GeoJSON 新选项
3.9+2024+持续优化,FOREIGN_MEMBERS_FEATURE 等 GeoJSON 高级特性

11.2 3.0 迁移注意事项

GDAL 3.0 是一次较大的 API 变动,Java 绑定受影响的主要变化:

  1. ogr.Feature.SetGeometry vs SetGeometryDirectly:前者转移所有权(不再需要手动 delete 传入的 Geometry),后者不转移。3.0 后推荐使用 SetGeometryDirectly 以避免所有权混淆。
  2. ogr.Driver.Open 签名变化:部分重载被废弃。
  3. osr.SpatialReference 方法重命名:少数方法从 CamelCase 调整为更一致的命名。
  4. 最低 Java 版本:构建要求升至 JDK 7+(3.0 前为 JDK 6+)。

11.3 与第三方框架的集成

框架集成方式说明
GeoServerImage I/O-Ext 插件GeoServer 通过 ImageIO-ext 的 gdalframework 模块调用 GDAL Java 绑定;需确保 gdal.jar 版本与服务器端 GDAL 匹配
Image I/O-Extgdalframework 核心模块提供丰富的格式支持框架,但不一定捆绑最新 GDAL
Spring Boot直接依赖 gdal.jar在 application.yml 中配置 java.library.path 或通过 System.loadLibrary 手动加载
Flink / Spark自定义 Source/Sink利用 GDAL 读写能力构建分布式地理空间 ETL

11.4 许可证

GDAL 采用 MIT/X 许可协议,允许商业使用、修改和再分发,仅需保留版权声明。Java 绑定同样遵循此许可。


12. 参考资料

官方文档

资源地址
GDAL 官网https://gdal.org
Java 绑定文档(官方)https://gdal.org/api/java/
Javadoc(API 参考)https://gdal.org/java/
GitHub 仓库https://github.com/OSGeo/gdal
Java 示例程序目录https://github.com/OSGeo/gdal/tree/master/swig/java/apps/
Maven Central (org.gdal)https://search.maven.org/search?q=g:org.gdal
构建文档(Java 选项)https://gdal.org/en/latest/development/building_from_source.html

社区与工具

资源说明
Tamas Szekeres' Windows SDKhttp://www.gisinternals.com/sdk — Win32/Win64 预编译包(含 Java 绑定)
OSGeo4Whttps://trac.osgeo.org/osgeo4w/ — Windows 安装包管理器
Image I/O-Exthttps://imageio-ext.dev.java.net/ — 基于 GDAL 绑定的 Java 图像 I/O 框架
gdal-java (Gitee 中文示例)https://gitee.com/zhong-liuyang/gdal-java — 中文注释的完整示例工程

相关书籍

  • 《GDAL 源码剖析与开发指南》— 李民录,人民邮电出版社,ISBN 9787115338990
  • 《GIS 开发核心技术——基于 GDAL 和 GeoServer》— 多篇技术文章汇编

文档生成时间:2026-09-13 信息来源:OSGeo/gdal 官方仓库(master 分支)、Context7 文档索引、Maven Central、公开技术资料 适用 GDAL 版本:3.x 系列(重点覆盖 3.8+)

更新于:

note