一、背景介绍
由于项目中需要用到大范围tiff的图像(全中国30米分辨率的dem影像),并且需要单点获取高程,以及实现部分范围的dem裁切与获取趋于范围极值,当时在网上查找的部分,很多都不满足预期,或者计算结果与实际并不够契合,因此单独开一篇专门讲这块内容。
二、 依赖引入
引入基础的依赖包,使用中间组件完成所需功能。
<!-- geotools依赖 --> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-shapefile</artifactId> <version>22.0</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-main</artifactId> <version>22.0</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-epsg-hsql</artifactId> <version>22.0</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-geotiff</artifactId> <version>22.0</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-geojson</artifactId> <version>22.0</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-api</artifactId> <version>20.0</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-metadata</artifactId> <version>22.0</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-opengis</artifactId> <version>22.0</version> <scope>compile</scope> </dependency> <!-- gdal依赖 --> <dependency> <groupId>org.gdal</groupId> <artifactId>gdal</artifactId> <version>3.0.0</version> </dependency> <!-- nga依赖 --> <dependency> <groupId>mil.nga</groupId> <artifactId>tiff</artifactId> <version>2.0.2</version> </dependency> <!-- jts依赖 --> <dependency> <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> <version>1.16.1</version> </dependency>由于组件之间存在交互关系,因此将其全部放到一起。
三、高程操作
3.0 pre-step: 基础环境配置
由于接下去的操作都需要依赖同一份数据源,因此在最开始的时候,需要先封装关于数据源的相关操作。
3.0.1 基础数据源初始化
3.0.1.1 GridCoverage2D创建
public void gridCoverage2D() throws IOException {
//读取基础dem文件
File file = new File(demFilePath);
//加载文件所需基础参数与投影参数
Hints tiffHints = new Hints();
tiffHints.add(new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE));
tiffHints.add(new Hints(Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM, DefaultGeographicCRS.WGS84));
//tiff读取器
GeoTiffReader tifReader = new GeoTiffReader(file, tiffHints);
//读取tiff,获得基础GridCoverage2D 。
GridCoverage2D coverage = tifReader.read(null);
3.0.1.2 TIFFImage创建
public TIFFImage tiffImage() {
File file = new File(demFileMin);
try {
return TiffReader.readTiff(file);
} catch (IOException e) {
e.printStackTrace();
return null;
3.1 获取指定经纬度高程
通过获取gridCoverage2D 的的投影信息,加载投影并使用经纬度进行计算位置点高程
public double getHeight(double lon, double lat) {
CoordinateReferenceSystem crs = gridCoverage2D.getCoordinateReferenceSystem2D();
DirectPosition position = new DirectPosition2D(crs, lon, lat);
int[] results = (int[]) gridCoverage2D.evaluate(position);
results = gridCoverage2D.evaluate(position, results);
return results[0];
3.2 获取区域范围极值
@ApiImplicitParams({
@ApiImplicitParam(name = "geometry", value = "飞行区域的数据", required = true, paramType = "GeoJSON"),
@ApiImplicitParam(name = "geometry_type", value = "图形数据格式(wkt,geojson),目前支持wkt与geojson格式,默认wkt", required = true, paramType = "GeoJSON"),
public Result<?> ConventionalTrajectoryPlanning(
@RequestParam(name = "geometry", required = true) String geometryString,
@RequestParam(name = "geometry_type", required = true, defaultValue = "wkt") String geometryType,
HttpServletRequest req) {
GeometryJSON geometryJSON = new GeometryJSON();
Geometry geometry = null;
try {
if (geometryType.equals("geojson")) {
geometry = geometryJSON.readPolygon(new ByteArrayInputStream(geometryString.getBytes()));
} else {
geometry = reader.read(geometryString);
Geometry geometryMercator = JTS.transform(geometry, mathTransform);
double area = geometryMercator.getArea() / 1000000;
if (area > heightArea) {
return Result.error("当前查询范围:" + String.format("%.2f", area) + "平方公里, " + "范围超出" + heightArea + "平方公里限制!");
} catch (IOException | ParseException | TransformException e) {
e.printStackTrace();
Result.error("GeoJSON输出异常,请检查输入的图形");
if (oConvertUtils.isEmpty(geometry)) return Result.error("输入图形为空,请检查");
Geometry gridPolygon = getRegionGrid(geometry, TIFF_RESOLUTION);
Coordinate[] coordinates = gridPolygon.getCoordinates();
int arrLength = coordinates.length;
double[] heightValueArr = new double[arrLength];
for (int i = 0; i < arrLength; i++) {
Coordinate coordinate = coordinates[i];
heightValueArr[i] = geotoolsTerrainHeight.getHeight(coordinate.x, coordinate.y);
return Result.ok(maxAndMin(heightValueArr, arrLength));
//引用的方法
//tiff的像元大小(30m*30m的DEM分辨率,一般可通过软件直接读取)
final double TIFF_RESOLUTION = 0.0002777777799991554275
* 获取区域的高程点列表
* @param geometry 输入的Geometry
* @param tiffResolution tiff的原始分辨率
* @return
private Geometry getRegionGrid(Geometry geometry, double tiffResolution) {
GeometryFactory geometryFactory = new GeometryFactory()
Envelope rect = geometry.getEnvelopeInternal();
double height = rect.getHeight();
double width = rect.getWidth();
int numX = (int) Math.ceil(width / tiffResolution);
int numY = (int) Math.ceil(height / tiffResolution);
double dx = (width - numX * tiffResolution) / 2.0;
double dy = (height - numY * tiffResolution) / 2.0;
Geometry[][] nodes = new Geometry[numX][numY];
for (int i = 0; i < numX; ++i) {
for (int j = 0; j < numY; ++j) {
double minX = dx + rect.getMinX() + i * tiffResolution;
double minY = dy + rect.getMinY() + j * tiffResolution;
double maxX = minX + tiffResolution;
double maxY = minY + tiffResolution;
Coordinate coord0 = new Coordinate(minX, minY);
Coordinate coord2 = new Coordinate(maxX, minY);
Coordinate coord3 = new Coordinate(maxX, maxY);
Coordinate coord4 = new Coordinate(minX, maxY);
Geometry box = geometryFactory.createPolygon(new Coordinate[]{coord0, coord2, coord3, coord4, coord0});
if (box.intersects(geometry)) {
Geometry region = null;
try {
region = geometry.intersection(box);
} catch (Exception e) {
e.printStackTrace();
try {
region = box.intersection(geometry);
} catch (Exception ee) {
e.printStackTrace();
log.info("获取交点失败!box: " + box + "\r\n geometry:" + geometry);
nodes[i][j] = region;
List<Geometry> list = new ArrayList<Geometry>();
for (int l = 0; l < numX; ++l) {
for (int m = 0; m < numY; ++m) {
Geometry region2 = nodes[l][m];
if (region2 != null) {
list.add(region2);
return geometryFactory.buildGeometry(list);
* 计算数组极值
* @param a
* @param length
* @return
public JSONObject maxAndMin(double[] a, int length) {
JSONObject jsonObject = new JSONObject();
double Max, Min;
double[] b, c;
if (length % 2 == 0) {
b = new double[length / 2];
c = new double[length / 2];
} else {
b = new double[length / 2 + 1];
c = new double[length / 2 + 1];
b[length / 2] = a[length - 1];
c[length / 2] = a[length - 1];
for (int i = 0, j = 0; i < length - 1; i += 2, j++) {
if (a[i] >= a[i + 1]) {
b[j] = a[i];
c[j] = a[i + 1];
} else {
c[j] = a[i];
b[j] = a[i + 1];
Max = b[0];
Min = c[0];
for (int i = 1; i < b.length; i++) {
if (Max < b[i]) Max = b[i];
for (int i = 1; i < c.length; i++) {
if (Min > c[i]) Min = c[i];
jsonObject.put("max", Max);
jsonObject.put("min", Min);
jsonObject.put("avg", Double.valueOf(String.format("%.2f",Arrays.stream(a).average().orElse(Double.NaN))));
return jsonObject;
3.3 区域DEM文件导出
@ApiOperation(value = "高程辅助-返回DEM文件流", notes = "高程辅助,返回DEM文件流")
@ApiImplicitParams({
@ApiImplicitParam(name = "wkt", value = "wkt格式的地理图形", required = true, paramType = "String"),
public void getDEM(
@RequestParam(name = "wkt", required = true) String wkt,
HttpServletRequest req, HttpServletResponse response) {
try {
/* 先用geotools给出一个基础图层,但这个基础图层使用nga的tiff包读不到像元尺度,因此再使用nga的tiff包重写 */
Geometry geometry = reader.read(wkt);
String prefix = UUID.randomUUID().toString().replace("-","");
//定义临时文件的后缀
String suffix =".tif";
File tempOutFile = File.createTempFile(prefix,suffix);
GridCoverage2D gridCoverage2D =geotoolsCoverageCrop(StringOperationUtils.gridCoverage2D, geometry);
GeoTiffWriter writer = new GeoTiffWriter(tempOutFile, tiffHints);
GeoTiffFormat geoTiffFormat = new GeoTiffFormat();
ParameterValueGroup writeParameters = geoTiffFormat.getWriteParameters();
java.util.List<GeneralParameterValue> valueList = writeParameters.values();
writer.write(gridCoverage2D, valueList.toArray(new GeneralParameterValue[valueList.size()]));
writer.dispose();
/* 使用nga的tiff包重写 */
TIFFImage tmpImage= TiffReader.readTiff(tempOutFile);
FileDirectory tmpFileDirectory = tmpImage.getFileDirectory();
Rasters rasters = tmpFileDirectory.readRasters();
double[] lowerCorner = gridCoverage2D.getEnvelope().getLowerCorner().getCoordinate();
double[] upperCorner = gridCoverage2D.getEnvelope().getUpperCorner().getCoordinate();
//左上角定点
double minX = lowerCorner[0];
double maxY = upperCorner[1];
//经纬度转点,X是经度,表宽;y是纬度,表高
FileDirectory fileDirectory = tiffImage.getFileDirectory();
//对新影像赋值
int width = rasters.getWidth(), height = rasters.getHeight();
Rasters newRaster = new Rasters(width, height, fileDirectory.getSamplesPerPixel(),
fileDirectory.getBitsPerSample().get(0), TiffConstants.SAMPLE_FORMAT_SIGNED_INT);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
newRaster.setFirstPixelSample(x , y , rasters.getPixel(x, y)[0]);
/* 更新参数 */
int rowsPerStrip = newRaster.calculateRowsPerStrip(TiffConstants.PLANAR_CONFIGURATION_PLANAR);
fileDirectory.setRowsPerStrip(rowsPerStrip);
fileDirectory.setImageHeight(height);
fileDirectory.setImageWidth(width);
fileDirectory.setWriteRasters(newRaster);
List<Double> doubles = new ArrayList<>();
doubles.add(0.00);
doubles.add(0.00);
doubles.add(0.00);
doubles.add(minX);
doubles.add(maxY);
doubles.add(0.00);
FileDirectoryEntry fileDirectoryEntry = new FileDirectoryEntry(FieldTagType.ModelTiepoint, FieldType.DOUBLE, 6, doubles);
fileDirectory.addEntry(fileDirectoryEntry);
TIFFImage tiffImage = new TIFFImage();
tiffImage.add(fileDirectory);
byte[] tiffBytes = TiffWriter.writeTiffToBytes(tiffImage);
response.getOutputStream().write(tiffBytes, 0, tiffBytes.length);
response.getOutputStream().flush();
/* 删除临时文件 */
tempOutFile.delete();
} catch (ParseException | IOException e) {
e.printStackTrace();
* 根据几何模型进行影像切割
* @param coverage2D 原始影像
* @param geom 几何模型
public static GridCoverage2D geotoolsCoverageCrop(GridCoverage2D coverage2D, Geometry geom) {
org.opengis.geometry.Envelope envelope = new Envelope2D();
((Envelope2D) envelope).setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84);
org.locationtech.jts.geom.Envelope jtsEnv = geom.getEnvelopeInternal();
((Envelope2D) envelope).height = jtsEnv.getHeight();
((Envelope2D) envelope).width = jtsEnv.getWidth();
((Envelope2D) envelope).x = jtsEnv.getMinX();
((Envelope2D) envelope).y = jtsEnv.getMinY();
CoverageProcessor processor = CoverageProcessor.getInstance();
final ParameterValueGroup param = processor.getOperation("CoverageCrop").getParameters();
param.parameter("Source").setValue(coverage2D);
param.parameter("Envelope").setValue(envelope);