图像瓦片应用
ImageLab教程涵盖了加载和渲染;本教程将演示如何使用CoverageProcessor和friends在覆盖率上直接执行基本操作(如裁剪和缩放),并使用Arguments工具简化命令行处理。我们将创建一个简单的实用程序应用程序来“平铺”一个覆盖范围(为了简单起见,只是简单地细分信封)并可选地缩放结果平铺
预先准备
其中许多可能已经在前面示例中的pom.xml中了,但至少要确保以下依赖项是可用的
<dependencies>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geotiff</artifactId>
<version>${geotools.version}</version>
</dependency>
</dependencies>
在 org.geotools.tutorial.coverage包中创建imagetier .java文件。覆盖和复制粘贴以下代码,其中包含我们的样板导入,字段和getter /setter:
package org.geotools.tutorial.coverage;
import java.io.File;
import java.io.IOException;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.coverage.grid.io.GridFormatFinder;
import org.geotools.coverage.processing.CoverageProcessor;
import org.geotools.coverage.processing.Operations;
import org.geotools.gce.geotiff.GeoTiffFormat;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.util.Arguments;
import org.geotools.util.factory.Hints;
import org.opengis.geometry.Envelope;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Simple tiling of a coverage based simply on the number vertical/horizontal tiles desired and
* subdividing the geographic envelope. Uses coverage processing operations.
*/
public class ImageTiler {
private final int NUM_HORIZONTAL_TILES = 16;
private final int NUM_VERTICAL_TILES = 8;
private Integer numberOfHorizontalTiles = NUM_HORIZONTAL_TILES;
private Integer numberOfVerticalTiles = NUM_VERTICAL_TILES;
private Double tileScale;
private File inputFile;
private File outputDirectory;
private String getFileExtension(File file) {
String name = file.getName();
try {
return name.substring(name.lastIndexOf(".") + 1);
} catch (Exception e) {
return "";
}
}
public Integer getNumberOfHorizontalTiles() {
return numberOfHorizontalTiles;
}
public void setNumberOfHorizontalTiles(Integer numberOfHorizontalTiles) {
this.numberOfHorizontalTiles = numberOfHorizontalTiles;
}
public Integer getNumberOfVerticalTiles() {
return numberOfVerticalTiles;
}
public void setNumberOfVerticalTiles(Integer numberOfVerticalTiles) {
this.numberOfVerticalTiles = numberOfVerticalTiles;
}
public File getInputFile() {
return inputFile;
}
public void setInputFile(File inputFile) {
this.inputFile = inputFile;
}
public File getOutputDirectory() {
return outputDirectory;
}
public void setOutputDirectory(File outputDirectory) {
this.outputDirectory = outputDirectory;
}
public Double getTileScale() {
return tileScale;
}
public void setTileScale(Double tileScale) {
this.tileScale = tileScale;
}
请注意,这并不是完整的代码清单——我们将在进行过程中完成它——所以如果您的IDE出现问题,现在不要担心
参数处理
因为我们正在创建一个命令行应用程序,所以我们需要处理命令行参数。GeoTools包含一个名为Arguments的类来促进这一点;我们将使用这个类来解析两个强制参数——输入文件和输出目录——以及一些可选参数——垂直和水平的瓦片计数和瓦片缩放。
public static void main(String[] args) throws Exception {
// GeoTools provides utility classes to parse command line arguments
Arguments processedArgs = new Arguments(args);
ImageTiler tiler = new ImageTiler();
try {
tiler.setInputFile(new File(processedArgs.getRequiredString("-f")));
tiler.setOutputDirectory(new File(processedArgs.getRequiredString("-o")));
tiler.setNumberOfHorizontalTiles(processedArgs.getOptionalInteger("-htc"));
tiler.setNumberOfVerticalTiles(processedArgs.getOptionalInteger("-vtc"));
tiler.setTileScale(processedArgs.getOptionalDouble("-scale"));
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
printUsage();
System.exit(1);
}
tiler.tile();
}
private static void printUsage() {
System.out.println(
"Usage: -f inputFile -o outputDirectory [-tw tileWidth<default:256> "
+ "-th tileHeight<default:256> ");
System.out.println(
"-htc horizontalTileCount<default:16> -vtc verticalTileCount<default:8>");
}
加载覆盖
首先,我们需要加载覆盖范围;GeoTools提供了GridFormatFinder和AbstractGridFormat来抽象地完成这一任务。注意:在写这篇文章的时候,GeoTiff处理有一个小问题,我们单独处理它。
private void tile() throws IOException {
AbstractGridFormat format = GridFormatFinder.findFormat(this.getInputFile());
String fileExtension = this.getFileExtension(this.getInputFile());
// working around a bug/quirk in geotiff loading via format.getReader which doesn't set this
// correctly
Hints hints = null;
if (format instanceof GeoTiffFormat) {
hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
}
GridCoverage2DReader gridReader = format.getReader(this.getInputFile(), hints);
GridCoverage2D gridCoverage = gridReader.read(null);
分割影像
接下来,我们将根据所请求的水平和垂直瓦片计数来细分覆盖率,方法是询问其信封的覆盖率,并将该信封水平和垂直地除以瓦片计数。这将给我们我们的瓦片信封的宽度和高度。然后我们将循环我们的水平和垂直瓷砖计数作物和规模
Envelope2D coverageEnvelope = gridCoverage.getEnvelope2D();
double coverageMinX = coverageEnvelope.getBounds().getMinX();
double coverageMaxX = coverageEnvelope.getBounds().getMaxX();
double coverageMinY = coverageEnvelope.getBounds().getMinY();
double coverageMaxY = coverageEnvelope.getBounds().getMaxY();
int htc =
this.getNumberOfHorizontalTiles() != null
? this.getNumberOfHorizontalTiles()
: NUM_HORIZONTAL_TILES;
int vtc =
this.getNumberOfVerticalTiles() != null
? this.getNumberOfVerticalTiles()
: NUM_VERTICAL_TILES;
double geographicTileWidth = (coverageMaxX - coverageMinX) / (double) htc;
double geographicTileHeight = (coverageMaxY - coverageMinY) / (double) vtc;
CoordinateReferenceSystem targetCRS = gridCoverage.getCoordinateReferenceSystem();
// make sure to create our output directory if it doesn't already exist
File tileDirectory = this.getOutputDirectory();
if (!tileDirectory.exists()) {
tileDirectory.mkdirs();
}
// iterate over our tile counts
for (int i = 0; i < htc; i++) {
for (int j = 0; j < vtc; j++) {
System.out.println("Processing tile at indices i: " + i + " and j: " + j);
// create the envelope of the tile
Envelope envelope =
getTileEnvelope(
coverageMinX,
coverageMinY,
geographicTileWidth,
geographicTileHeight,
targetCRS,
i,
j);
GridCoverage2D finalCoverage = cropCoverage(gridCoverage, envelope);
if (this.getTileScale() != null) {
finalCoverage = scaleCoverage(finalCoverage);
}
// use the AbstractGridFormat's writer to write out the tile
File tileFile = new File(tileDirectory, i + "_" + j + "." + fileExtension);
format.getWriter(tileFile).write(finalCoverage, null);
}
}
}
创建包络线
我们将根据我们的索引和目标包络线的宽度和高度创建瓦片的包络线:
private Envelope getTileEnvelope(
double coverageMinX,
double coverageMinY,
double geographicTileWidth,
double geographicTileHeight,
CoordinateReferenceSystem targetCRS,
int horizontalIndex,
int verticalIndex) {
double envelopeStartX = (horizontalIndex * geographicTileWidth) + coverageMinX;
double envelopeEndX = envelopeStartX + geographicTileWidth;
double envelopeStartY = (verticalIndex * geographicTileHeight) + coverageMinY;
double envelopeEndY = envelopeStartY + geographicTileHeight;
return new ReferencedEnvelope(
envelopeStartX, envelopeEndX, envelopeStartY, envelopeEndY, targetCRS);
}
裁剪
现在我们已经有了tile envelope宽度和高度,我们将遍历tile count并根据目标envelope进行裁剪。在本例中,我们将手动创建参数并使用覆盖率处理器来执行CoverageCrop操作。在下一步中,我们将遇到执行覆盖操作的更简单的方法。
private GridCoverage2D cropCoverage(GridCoverage2D gridCoverage, Envelope envelope) {
CoverageProcessor processor = CoverageProcessor.getInstance();
// An example of manually creating the operation and parameters we want
final ParameterValueGroup param = processor.getOperation("CoverageCrop").getParameters();
param.parameter("Source").setValue(gridCoverage);
param.parameter("Envelope").setValue(envelope);
return (GridCoverage2D) processor.doOperation(param);
}
缩放
我们可以使用缩放操作来选择缩放我们的瓦片。在本例中,我们将使用Operations类来简化我们的工作。这个类包装了操作,并为它们提供了一个稍微更类型安全的接口。在这里,我们将按相同的比例调整X和Y维度,以保持原始覆盖率的高宽比。
/**
* Scale the coverage based on the set tileScale
*
* <p>As an alternative to using parameters to do the operations, we can use the Operations
* class to do them in a slightly more type safe way.
*
* @param coverage the coverage to scale
* @return the scaled coverage
*/
private GridCoverage2D scaleCoverage(GridCoverage2D coverage) {
Operations ops = new Operations(null);
coverage =
(GridCoverage2D)
ops.scale(coverage, this.getTileScale(), this.getTileScale(), 0, 0);
return coverage;
}
}
运行程序
用ide运行
如果您一直使用IDE,那么内建的运行功能是运行应用程序的最简单方法。例如,在Eclipse中,我们可以从菜单中选择Run -> Run Configurations并使用以下配置创建新的Java应用程序配置。
image.png
在Arguments选项卡下,我们将把应用程序指向下载的光栅数据,给它一个16x8的瓦片计数,将它输出给一个临时控制器,并将瓦片缩放两倍。
-f /Users/devon/Downloads/NE2_50M_SR_W/NE2_50M_SR_W.tif -htc 16 -vtc 8 -o /Users/devon/tmp/tiles -scale 2.0
请确保将文件名和目录替换为您自己的位置
如果您正在使用Eclipse,那么这些路径可以被提示符替换。在运行对话框中选择变量并使用file_prompt和folder_prompt变量。
image.png
最后,单击Run运行我们的应用程序。你可能会看到一些与ImageIO相关的警告信息,但这些都是正常的。
除Eclipse外的其他ide,如Netbeans和IntelliJ,都有非常类似的Java应用程序运行选项
用maven运行
如果您不使用IDE,那么运行我们的应用程序的最简单方法就是使用Maven exec任务来运行我们的应用程序,详情参见Maven Quickstart。我们只需将Maven Shade插件添加到pom.xml中
mvn exec:java -Dexec.mainClass=org.geotools.tutorial.ImageTiler -Dexec.args="-f /Users/devon/Downloads/NE2_50M_SR_W/NE2_50M_SR_W.tif -htc 16 -vtc 8 -o /Users/devon/tmp/tiles -scale 2.0"
扩展
- 有关覆盖率上可用操作的更多信息,请参阅覆盖率处理器文档。CoverageProcessor中可用的操作之一是Resample(参见操作类),我们可以使用它非常容易地重新投射我们的覆盖率。尝试重新投射覆盖到EPSG:3587(谷歌的网墨卡托投影)。
- 我们可以通过将tiles加载到GeoServer ImageMosaic存储中来验证它们是否正常。或者,我们可以通过编程创建一个指向目录文件的ImageMosaicReader并从中读取。