基于Java/Scala、GeoTools/GeoTrellis将Sentinel-2的JP2转为多波段GeoTIFF
嘿,针对你处理Sentinel-2 JP2数据的需求,结合你熟悉的GeoTools和GeoTrellis,我整理了具体的实现方案,应该能解决你的两个问题:
一、使用GeoTools实现
问题a:给定范围坐标将JP2转为带地理参考的GeoTIFF
先提个小细节:Sentinel-2 L1C的JP2其实是附带地理信息的,可能你没同步拿到对应的元数据文件?不过没关系,我们可以手动为栅格赋予坐标参考和范围。步骤如下:
- 读取JP2文件并转为
GridCoverage2D - 指定对应的坐标参考系(你提供的tile是10S EG,对应UTM Zone 10S,EPSG代码是32710)
- 根据你给定的坐标范围创建
Envelope - 将地理信息绑定到栅格后写入GeoTIFF
代码示例(Java):
import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.gce.geotiff.GeoTiffWriter; import org.opengis.referencing.crs.CoordinateReferenceSystem; import javax.imageio.ImageIO; import java.io.File; import java.awt.image.RenderedImage; public class Jp2ToGeoTiff { public static void main(String[] args) throws Exception { // 1. 读取JP2文件 File jp2File = new File("path/to/your/band.jp2"); RenderedImage image = ImageIO.read(jp2File); GridCoverage2D rawCoverage = new GridCoverageFactory().create("raw_band", image, null); // 2. 设置坐标参考系(UTM 10S,EPSG:32710) CoordinateReferenceSystem crs = CRS.decode("EPSG:32710"); // 3. 定义地理范围(替换成你给定的minX, minY, maxX, maxY) ReferencedEnvelope envelope = new ReferencedEnvelope(minX, maxX, minY, maxY, crs); // 4. 绑定地理信息并写入GeoTIFF GridCoverage2D geoCoverage = new GridCoverageFactory().create( "geo_band", rawCoverage.getRenderedImage(), envelope, rawCoverage.getSampleDimensions(), null, null ); GeoTiffWriter writer = new GeoTiffWriter(new File("path/to/output/band.tif")); writer.write(geoCoverage, null); writer.dispose(); } }
问题b:合并多波段为单个GeoTIFF
要合并多个单波段栅格,核心是确保所有栅格的坐标参考系、地理范围和分辨率完全一致,然后用GridCoverageFactory将它们组合成多波段栅格:
import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.gce.geotiff.GeoTiffReader; import org.geotools.gce.geotiff.GeoTiffWriter; import java.io.File; import java.util.Arrays; import java.util.List; public class MergeBands { public static void main(String[] args) throws Exception { // 1. 读取所有单波段GeoTIFF(或直接从JP2处理后的GridCoverage2D) List<File> bandFiles = Arrays.asList( new File("path/to/band1.tif"), new File("path/to/band2.tif"), new File("path/to/band3.tif") // 添加其他波段文件 ); List<GridCoverage2D> bandCoverages = bandFiles.stream() .map(file -> { try { return new GeoTiffReader(file).read(null); } catch (Exception e) { throw new RuntimeException(e); } }) .toList(); // 2. 合并为多波段栅格 GridCoverage2D multiBandCoverage = new GridCoverageFactory().create( "sentinel2_multi_band", new javax.media.jai.MultiRenderedImage( bandCoverages.stream() .map(GridCoverage2D::getRenderedImage) .toArray(RenderedImage[]::new) ), bandCoverages.get(0).getEnvelope(), bandCoverages.stream() .flatMap(cov -> Arrays.stream(cov.getSampleDimensions())) .toArray(SampleDimension[]::new), null, null ); // 3. 写入多波段GeoTIFF GeoTiffWriter writer = new GeoTiffWriter(new File("path/to/output/multi_band.tif")); writer.write(multiBandCoverage, null); writer.dispose(); } }
二、使用GeoTrellis(Scala)实现
如果你更倾向用Scala,GeoTrellis的栅格处理API更简洁,适合批量操作:
问题a:给定范围坐标转JP2为GeoTIFF
GeoTrellis支持直接读取JP2,然后绑定地理信息后写入GeoTIFF:
import geotrellis.raster._ import geotrellis.raster.io.geotiff._ import geotrellis.proj4._ import java.io.File object Jp2ToGeoTiff { def main(args: Array[String]): Unit = { // 读取JP2文件 val jp2Path = "path/to/your/band.jp2" val rawTile = GeoTiffReader.readSingleband(jp2Path).tile // 设置坐标参考系(UTM 10S,EPSG:32710) val crs = CRS.fromEpsgCode(32710) // 定义地理范围(替换成你的坐标) val extent = Extent(minX, minY, maxX, maxY) // 创建带地理信息的单波段GeoTIFF并写入 val singlebandTiff = SinglebandGeoTiff(rawTile, extent, crs) singlebandTiff.write("path/to/output/band.tif") } }
问题b:合并多波段为单个GeoTIFF
GeoTrellis的MultibandTile可以直接组合多个单波段Tile,前提是所有Tile的Extent和CRS一致:
import geotrellis.raster._ import geotrellis.raster.io.geotiff._ import java.io.File object MergeBands { def main(args: Array[String]): Unit = { // 读取所有单波段GeoTIFF val bandPaths = List( "path/to/band1.tif", "path/to/band2.tif", "path/to/band3.tif" // 添加其他波段 ) val singlebandTiffs = bandPaths.map(GeoTiffReader.readSingleband) // 确保所有栅格的Extent和CRS一致(如果不一致需要先重采样对齐) val baseExtent = singlebandTiffs.head.extent val baseCrs = singlebandTiffs.head.crs // 合并为多波段Tile val multibandTile = MultibandTile(singlebandTiffs.map(_.tile)) // 创建多波段GeoTIFF并写入 val multibandTiff = MultibandGeoTiff(multibandTile, baseExtent, baseCrs) multibandTiff.write("path/to/output/multi_band.tif") } }
额外提示
如果你的Sentinel-2 JP2其实带有元数据(比如MTD_MSIL1C.xml文件),可以读取元数据自动获取Extent和CRS,这样比手动设置更准确,避免坐标偏差。比如GeoTrellis可以解析Sentinel-2的元数据文件来直接获取栅格的地理参数。
内容的提问来源于stack exchange,提问作者user3059517
相关产品推荐
相关产品推荐

