DEM地形操作(geotools方式与nga方式)

一、背景介绍

由于项目中需要用到大范围tiff的图像(全中国30米分辨率的dem影像),并且需要单点获取高程,以及实现部分范围的dem裁切与获取趋于范围极值,当时在网上查找的部分,很多都不满足预期,或者计算结果与实际并不够契合,因此单独开一篇专门讲这块内容。

二、 依赖引入

引入基础的依赖包,使用中间组件完成所需功能。


       
        
            org.geotools
            gt-shapefile
            22.0
        
        
            org.geotools
            gt-main
            22.0
        
        
            org.geotools
            gt-epsg-hsql
            22.0
        
        
            org.geotools
            gt-geotiff
            22.0
        
        
            org.geotools
            gt-geojson
            22.0
        
        
            org.geotools
            gt-api
            20.0
        
        
            org.geotools
            gt-metadata
            22.0
        
        
            org.geotools
            gt-opengis
            22.0
            compile
        

        
        
            org.gdal
            gdal
            3.0.0
        
        
        
            mil.nga
            tiff
            2.0.2
        
        
        
            org.locationtech.jts
            jts-core
            1.16.1
        

由于组件之间存在交互关系,因此将其全部放到一起。

三、高程操作

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 list = new ArrayList();
        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 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 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);

        return (GridCoverage2D) processor.doOperation(param);
    }

四、总结

总体来说,整个的实现流程基本上可以概括为[引入依赖]->[加载数据源]->[业务数据读取]。读取DEM的高程流程相对简单,获取范围内的高程极值操作也不复杂,主要是需要算出范围对应的格网,以及获取每个格网像元的高程值;较为复杂的是DEM的图像导出,由于使用geotools导出的dem图像存在一定的偏移与投影问题,导致其他平台软件无法正常读取dem的一些属性信息,因此在操作上,先使用geotools自定义的导出,先输出一份DEM,再使用正常平台导出的dem文件,通过读取它的属性信息,赋值给空的dem影像,再将像元的高程值写入,最后输出成一份符合规范的dem。此操作较为繁琐,但基础满足功能所需,此为抛砖引玉,如有更好的办法实现,望诸位不吝赐教,将感激不尽!

你可能感兴趣的:(DEM地形操作(geotools方式与nga方式))