一、前言为什么需要栅格数据处理在真实 GIS 系统中对于二维数据除了矢量数据还有另一大核心数据类型栅格Raster。典型栅格数据场景DEM 数字高程模型提取某点的海拔气象 GeoTIFF提取某地的温度 / 降水量土地利用栅格统计某区域内各类用地占比卫星影像波段合成、裁剪、重采样本篇将带你入门 GeoTools 的 Coverage 体系掌握 GeoTIFF 的读取、元信息提取和像素值查询。二、核心概念速览GeoTools 中栅格数据和矢量数据的 API 体系是完全独立的矢量数据DataStore/FeatureSource/SimpleFeature栅格数据GridCoverage2D/GeoTiffReader/SampleDimension核心类说明GridCoverage2D栅格覆盖层的抽象代表一个地理参考的栅格数据集GeoTiffReaderGeoTIFF 格式专用读取器GridSampleDimension描述每个波段的采样维度名称、单位、NoData 值等DirectPosition地理坐标点用于像素值提取CRS.findMathTransform()坐标转换经纬度 ↔ 栅格坐标系三、Maven 依赖在已有 GeoTools 依赖基础上新增gt-geotiffdependencygroupIdorg.geotools/groupIdartifactIdgt-geotiff/artifactIdversion29.3/version/dependency四、完整可运行代码importorg.geotools.coverage.grid.GridCoverage2D;importorg.geotools.gce.geotiff.GeoTiffReader;importorg.geotools.geometry.DirectPosition2D;importorg.geotools.referencing.CRS;importorg.opengis.coverage.PointOutsideCoverageException;importorg.opengis.geometry.DirectPosition;importorg.opengis.referencing.FactoryException;importorg.opengis.referencing.crs.CoordinateReferenceSystem;importorg.opengis.referencing.operation.MathTransform;importorg.opengis.referencing.operation.TransformException;importjava.io.File;importjava.io.IOException;publicclassGeoToolsRasterDemo{publicstaticvoidmain(String[]args)throwsException{StringtiffPathD:/testdata/testtif/NE1_LR_LC/NE1_LR_LC.tif;GridCoverage2Dcoveragenull;GeoTiffReaderreadernull;try{readernewGeoTiffReader(newFile(tiffPath));coveragereader.read(null);// 1. 基本信息printCoverageInfo(coverage);// 2. 提取指定坐标的像素值北京附近extractPixelValue(coverage,116.4,39.9);}finally{if(reader!null){reader.dispose();}if(coverage!null){coverage.dispose(true);}}}// 打印栅格信息 privatestaticvoidprintCoverageInfo(GridCoverage2Dcoverage){System.out.println( GeoTIFF 信息 );// 坐标系CoordinateReferenceSystemcrscoverage.getCoordinateReferenceSystem();System.out.println(坐标系: crs.getName());// 空间范围varenvelopecoverage.getEnvelope();System.out.println(范围: envelope);// 网格大小vargridcoverage.getGridGeometry();intwidthgrid.getGridRange().getSpan(0);intheightgrid.getGridRange().getSpan(1);System.out.println(像素尺寸: width x height);// 波段数intbandscoverage.getSampleDimensions().length;System.out.println(波段数: bands);}// 提取像素值 privatestaticvoidextractPixelValue(GridCoverage2Dcoverage,doublelon,doublelat)throwsFactoryException,TransformException{CoordinateReferenceSystemdataCRScoverage.getCoordinateReferenceSystem();CoordinateReferenceSystemtargetCRSCRS.decode(EPSG:4326,true);MathTransformtransformCRS.findMathTransform(targetCRS,dataCRS);DirectPositionsourcePtnewDirectPosition2D(targetCRS,lon,lat);DirectPositiontargetPtnewDirectPosition2D(dataCRS);transform.transform(sourcePt,targetPt);try{double[]valuescoverage.evaluate(targetPt,(double[])null);System.out.println(\n 像素值提取 );System.out.println(坐标 (lon, lat));for(inti0;ivalues.length;i){System.out.println(波段 i: values[i]);}}catch(PointOutsideCoverageExceptione){System.out.println(\n警告: 坐标超出栅格范围);}}}五、代码逐段讲解5.1 资源管理与关闭GeoTools 29.x 中栅格相关资源必须手动关闭不能用 try-with-resources// GeoTiffReader 不是 AutoCloseable不能用 try-with-resourcesGeoTiffReaderreadernull;try{readernewGeoTiffReader(newFile(tiffPath));// ...}finally{if(reader!null)reader.dispose();if(coverage!null)coverage.dispose(true);}关键区别reader.dispose()无参释放文件句柄coverage.dispose(true)必须传布尔值true表示强制释放5.2 打印栅格元信息GridCoverage2D提供了一系列方法获取栅格的基本信息// 坐标系CRSCoordinateReferenceSystemcrscoverage.getCoordinateReferenceSystem();// 地理范围经纬度边界varenvelopecoverage.getEnvelope();// 像素尺寸宽 x 高intwidthgrid.getGridRange().getSpan(0);intheightgrid.getGridRange().getSpan(1);// 波段数29.x 返回 GridSampleDimension[]intbandscoverage.getSampleDimensions().length;注意getSampleDimensions()返回的是数组GridSampleDimension[]不是 List因此要用.length而不是.size()。5.3 提取像素值核心流程经纬度坐标 → 坐标转换 → 栅格内部坐标 →evaluate()// 1. 构建坐标转换EPSG:4326 - 栅格坐标系MathTransformtransformCRS.findMathTransform(targetCRS,dataCRS);// 2. 构造源坐标点经度, 纬度DirectPositionsourcePtnewDirectPosition2D(targetCRS,lon,lat);// 3. 执行坐标转换transform.transform(sourcePt,targetPt);// 4. 提取像素值返回值数组长度 波段数double[]valuescoverage.evaluate(targetPt,(double[])null);返回的double[]中每个元素对应一个波段的像素值。对于单波段 DEMvalues[0]就是海拔。六、运行结果示例以上述代码运行NE1_LR_LC.tif为例控制台输出如下 GeoTIFF 信息 坐标系: GCS_WGS_1984 范围: GeneralEnvelope[(-180.0, -89.999999999982), (179.99999999996402, 90.0)] 像素尺寸: 16200 x 8100 波段数: 3 像素值提取 坐标 (116.4, 39.9) 波段 0: 227.0 波段 1: 221.0 波段 2: 190.0七、常见问题与注意事项7.1coverage.dispose()必须传布尔值// 错误写法编译失败coverage.dispose();// 正确写法coverage.dispose(true);7.2GeoTiffReader不是AutoCloseable// 错误写法编译失败try(GeoTiffReaderreadernewGeoTiffReader(file)){returnreader.read(null);}// 正确写法GeoTiffReaderreadernull;try{readernewGeoTiffReader(file);returnreader.read(null);}finally{if(reader!null)reader.dispose();}7.3 波段数用.length不是.size()// 错误写法GridSampleDimension 没有 size() 方法intbandscoverage.getSampleDimensions().size();// 正确写法29.x 返回的是数组intbandscoverage.getSampleDimensions().length;7.4 像素坐标不等于地理坐标GeoTIFF 内部使用像素坐标行列号而业务代码通常使用地理坐标经纬度。必须通过CRS.findMathTransform()进行转换不能直接把经纬度传给evaluate()。7.5 坐标超出栅格范围的处理try{double[]valuescoverage.evaluate(targetPt,(double[])null);}catch(PointOutsideCoverageExceptione){System.out.println(警告: 坐标超出栅格范围);}7.6 多波段影像的返回值顺序coverage.evaluate()返回的double[]中索引顺序与波段顺序一致。例如 Landsat 8 的波段顺序为Band 1coastal aerosol、Band 2blue、Band 3green…… 需要结合具体影像的元数据解读。八、工程实践建议始终在finally中释放资源readercoverage避免文件句柄泄漏读取前先检查 GeoTIFF 的 CRS避免无意义的 Transform 计算对于大 TIFF 文件考虑使用 ImageIO 的流式读取避免一次性加载多波段影像的像素值提取要注意 NoData 值的过滤生产环境建议将常用 GeoTIFF 的元数据CRS / 范围 / 波段数缓存起来批量像素提取时复用MathTransform对象避免重复创建九、小结本篇介绍了 GeoTools 栅格数据处理的核心能力。与矢量数据不同栅格数据使用GridCoverage2D体系需要特别注意资源管理和坐标转换。三个关键要点GeoTiffReader和GridCoverage2D都需要手动dispose且dispose的签名不同像素值提取必须先做坐标转换经纬度 → 栅格 CRSgetSampleDimensions()返回的是数组用.length获取波段数