GDAL Java 绑定使用指南
简介
GDAL (Geospatial Data Abstraction Library) 是一个开源的地理空间数据转换库。Java 绑定允许在 Java 应用程序中使用 GDAL 的功能。
安装步骤
1. 系统要求
- Java Development Kit (JDK) 8 或更高版本
- Maven 或 Gradle 构建工具
- 原生 GDAL 库
2. 安装原生 GDAL 库
Windows
# 使用 OSGeo4W 安装
# 下载并运行 OSGeo4W 安装程序
# 选择安装 GDAL 组件Linux (Ubuntu/Debian)
sudo apt-get update
sudo apt-get install gdal-bin libgdal-devmacOS
brew install gdal3. 配置环境变量
Windows
set GDAL_HOME=C:\OSGeo4W
set PATH=%PATH%;%GDAL_HOME%\bin
set JAVA_LIBRARY_PATH=%GDAL_HOME%\binLinux/macOS
export GDAL_HOME=/usr
export LD_LIBRARY_PATH=$GDAL_HOME/lib:$LD_LIBRARY_PATH
export JAVA_LIBRARY_PATH=$GDAL_HOME/lib4. Maven 依赖
<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 包装成 JAIRenderedOp,适合做影像处理管线;本笔记以官方绑定为准。
5. Gradle 依赖
dependencies {
implementation 'org.gdal:gdal:3.11.0'
}6. 原生库加载方式
Java 绑定通过 JNI 调用原生 GDAL,有三种常见加载方式:
-Djava.library.path(最常用):把 GDAL 的 lib/bin 目录加进去,见下文"常见问题"。- JNA/JNI 自动定位:部分发行版(如
gdal-utils附带工具类)支持从 classpath 解压 dll/so 后System.load()。 - 打包进 fat-jar:不推荐;dll 需要落盘才能被 JVM 加载,通常用 maven-dependency-plugin 在启动前解到临时目录。
验证安装是否成功:
gdal.AllRegister();
System.out.println(gdal.VersionInfo("VERSION_NUM")); // 例如 3110000 = 3.11.0基本用法
初始化 GDAL
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里)。
读取栅格数据
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.Openvsgdal.OpenEx:OpenEx支持 flags(如OF_VECTOR、OF_RASTER、OF_READONLY),还能直接打开带子数据集的格式(如file.tif/subdataset=1)。ReadRaster一次性读整幅大图容易 OOM,生产环境按块读(见"性能优化")。- 所有返回的 SWIG 包装对象(
Dataset、Band、SpatialReference、Feature…)都持有原生内存,用完必须delete()(或依赖 try-with-resources)。
写入栅格数据
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+ 压缩 + 金字塔(见下文)。
矢量数据操作
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();
}
}
}
}创建矢量数据
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 几何类型常量。
坐标转换
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:
4326WGS84 经纬度、3857Web Mercator(Google/OSM 瓦片)、4490CGCS2000、32650UTM 50N;国内常见地方坐标系用 EPSG:4610~4619 系列(CGCS2000 / 北京54 / 西安80 的 3 度带与高斯投影,具体带号按所在经度查)。 - 更高精度场景可给
SpatialReference设置七参数(SetTOWGS84)或自定义 gridshift。
高级操作
重投影栅格(gdal.Warp)
Java 绑定把 C++ 的 warp 能力暴露为 gdal 类的静态方法,选项用 String[] 传,键名与 gdalwarp CLI 相同(不带前导 -):
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,注意与 Pythongdal.WarpOptions的outputBounds语义一致)、tr=(目标分辨率)、srcNodata=/dstNodata=、cutline=、cropToCutline=YES、multi=CREATE_ONLY(多线程分块写)。 - 需要精细控制输出尺寸/对齐时,可用
gdal.AutoCreateWarpedDataset先算出目标数据集再WarpCreateCopy(签名随版本略有差异,以所用 jar 的 javadoc 为准)。
按矢量边界裁剪
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 里通常分两步:
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):
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)
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)
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.pathJVM 参数:bashjava -Djava.library.path=/path/to/gdal/lib -jar your-app.jar
2. 驱动程序未注册
错误: 无法打开特定格式的文件
解决方案:
// 在使用前调用
gdal.AllRegister();
ogr.RegisterAll();3. 内存管理
GDAL Java 绑定使用原生资源,需要手动释放:
Dataset ds = gdal.Open("file.tif", gdalconstConstants.GA_ReadOnly);
try {
// 使用数据集
} finally {
ds.delete(); // 重要:释放原生资源
}性能优化建议
1. 分块读取大栅格(防 OOM)
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 自己的子窗口":
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. 其他要点
- 重用对象:
CoordinateTransformation、Driver等构建成本高,缓存复用,别在循环里 new。 - 创建选项:发布用影像固定加
TILED=YES+COMPRESS=LZW/DEFLATE+ 金字塔;大数据量加BIGTIFF=IF_SAFER。 - 内存映射:只读场景可用
MEM:驱动把小数据集整体载入内存,后续读写全走 RAM。 - 日志:
gdal.SetConfigOption("CPL_LOG", "/tmp/gdal.log")或"CPL_DEBUG"排查驱动问题。
参考资源
- GDAL 官方文档
- GDAL Java 绑定源码(swig/java) —— Java API 以随 jar 发布的 javadoc 为准
- Maven Central: org.gdal
- GitHub 仓库
GDAL Java 绑定(Java Bindings)全面技术总结
基于 OSGeo/gdal 官方仓库、Context7 文档与公开技术资料整理,涵盖架构、安装、API、示例代码与最佳实践。
目录
- 概述
- 核心概念与架构
- 环境搭建与依赖管理
- 栅格数据 API(Raster)
- 矢量数据 API(OGR Vector)
- 空间参考与坐标变换(OSR)
- 常用工具类与回调机制
- 完整示例程序
- 性能优化与线程安全
- 常见问题与故障排查
- 版本演进与兼容性
- 参考资料
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:
<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
# 方式一:设置环境变量(当前会话有效)
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 ldconfigmacOS
export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH
# Homebrew 安装的 GDAL 若未启用 Java 绑定,需源码编译并指定 --with-javaWindows
set PATH=C:\gdal\bin;C:\gdal\java;%PATH%
:: gdalalljni.dll 放在 bin 目录或 java 目录均可Windows 预编译包推荐来源:
- Tamas Szekeres' gisinternals.com SDK — 包含 Win32/Win64 完整二进制 + Java 绑定
- OSGeo4W 安装包(选择
gdal+gdal-java组件)
3.3 从源码构建
# 前置: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_BINDINGS | ON(检测到 JDK 时) | 是否构建 Java 绑定 |
GDAL_JAVA_INSTALL_DIR | ${DATADIR}/java | gdal.jar 安装目录 |
GDAL_JAVA_JNI_INSTALL_DIR | (3.8+) | JNI 本地库安装目录 |
3.4 验证安装
# 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 测试类:
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 初始化与打开数据集
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_ReadOnly | 0 | 只读 |
GA_Update | 1 | 读写 |
GA_Extended | 2 | 扩展模式(某些驱动需要) |
4.2 读取基本信息
// 尺寸
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 波段读取(核心)
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 波段写入与创建新数据集
// 创建新的 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_Byte | uint8 | 1 | 无符号 8 位 |
GDT_UInt16 | uint16 | 2 | 无符号 16 位 |
GDT_Int16 | int16 | 2 | 有符号 16 位 |
GDT_UInt32 | uint32 | 4 | 无符号 32 位 |
GDT_Int32 | int32 | 4 | 有符号 32 位 |
GDT_Float32 | float | 4 | 单精度浮点 |
GDT_Float64 | double | 8 | 双精度浮点 |
GDT_CInt16 | c-int16 | 4 | 复数(实部 int16) |
GDT_CFloat32 | c-float32 | 8 | 复数(实部 float32) |
4.5 统计信息与质量评估
// 精确统计(全图扫描,较慢)
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)
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)
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 颜色表与调色板
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 初始化与打开数据源
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 遍历图层与要素
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 查询
// 空间过滤:只读取与给定几何相交的要素
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 创建矢量数据
// 创建 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 几何操作
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 Shapefile | ESRI Shapefile (.shp) |
GeoJSON | GeoJSON |
GPKG | GeoPackage |
SQLite | SQLite (+ SpatiaLite) |
Memory | 内存临时层 |
KML / LIBKML | KML / KMZ |
CSV | CSV(无几何或 WKT 列) |
FlatGeobuf | FlatGeobuf |
GML | GML 2/3 |
DXF | AutoCAD DXF |
MapInfo File | MapInfo TAB |
PostgreSQL | PostGIS |
MySQL | MySQL Spatial |
6. 空间参考与坐标变换(OSR)
6.1 SpatialReference 基本操作
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 坐标变换
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 完成:
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 | 三次样条 | 平滑插值 |
GLanczos | Lanczos | 最高质量(慢) |
GAverage | 平均 | 降采样聚合 |
GRMS | 均方根 | 降采样保留方差 |
7. 常用工具类与回调机制
7.1 进度回调
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)
// 等价于设置环境变量,优先级高于实际环境变量
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_ENCODING | Shapefile 编码(UTF-8 / GBK 等) |
7.3 日志与错误处理
// 错误码常量
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 中直接使用路径前缀即可:
// 普通文件
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)
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 简化版)
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 坐标批量转换工具
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 栅格裁剪与子集读取
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 多线程注意事项
// 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 大数据集处理模式
// 流式处理模式:边读边处理边丢弃,峰值内存恒定
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 搜索路径中。
解决:
# 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。
解决:
// 打开前设置编码
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。
// 错误
Band b0 = ds.GetRasterBand(0); // null!
// 正确
Band b1 = ds.GetRasterBand(1); // 第一个波段10.6 多线程下随机崩溃(段错误)
原因:GDAL 未以多线程模式编译,GC 线程与主线程竞争原生资源。
解决:重新编译 GDAL 时确保多线程支持开启(CMake 默认开启;autoconf 需加 --with-threads)。
10.7 打开远程文件超时
gdal.SetConfigOption("CPL_VSIL_CURL_TIMEOUT", "60"); // 连接超时
gdal.SetConfigOption("CPL_VSIL_CURL_CONNECT_TIMEOUT", "10"); // 建连超时
gdal.SetConfigOption("CPL_VSIL_CURL_RETRY", "3"); // 重试次数10.8 如何确认某个格式驱动是否可用
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.0 | 2008 | Java 绑定首次随官方发布,Javadoc 开始覆盖 |
| 1.11.x | 2012 | 稳定期,Maven Central 最早收录的版本之一 |
| 2.0 | 2016 | OGR 引入 OGRLayer::GetNextByIndex,Java 同步更新 |
| 2.4 | 2018 | 引入 SetFields 批量属性写入;GeoJSON 驱动增强 |
| 3.0 | 2019 | 重大重构:ogr 包重组,部分类迁移;废弃 OGRGeometry::transform 旧签名;要求 C++11 |
| 3.1 | 2020 | 新增 ExecuteSQL 支持更多方言;BuildOverviews 改进 |
| 3.4 | 2021 | GDALWarp 选项扩展;/vsis3/ 改进 |
| 3.8 | 2023 | JNI 库安装路径分离(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 绑定受影响的主要变化:
ogr.Feature.SetGeometryvsSetGeometryDirectly:前者转移所有权(不再需要手动 delete 传入的 Geometry),后者不转移。3.0 后推荐使用SetGeometryDirectly以避免所有权混淆。ogr.Driver.Open签名变化:部分重载被废弃。osr.SpatialReference方法重命名:少数方法从 CamelCase 调整为更一致的命名。- 最低 Java 版本:构建要求升至 JDK 7+(3.0 前为 JDK 6+)。
11.3 与第三方框架的集成
| 框架 | 集成方式 | 说明 |
|---|---|---|
| GeoServer | Image I/O-Ext 插件 | GeoServer 通过 ImageIO-ext 的 gdalframework 模块调用 GDAL Java 绑定;需确保 gdal.jar 版本与服务器端 GDAL 匹配 |
| Image I/O-Ext | gdalframework 核心模块 | 提供丰富的格式支持框架,但不一定捆绑最新 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 SDK | http://www.gisinternals.com/sdk — Win32/Win64 预编译包(含 Java 绑定) |
| OSGeo4W | https://trac.osgeo.org/osgeo4w/ — Windows 安装包管理器 |
| Image I/O-Ext | https://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+)
