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

一展鸡、背景介紹

由于項目中需要用到大范圍tiff的圖像(全中國30米分辨率的dem影像),并且需要單點獲取高程埃难,以及實現(xiàn)部分范圍的dem裁切與獲取趨于范圍極值莹弊,當時在網(wǎng)上查找的部分,很多都不滿足預期涡尘,或者計算結(jié)果與實際并不夠契合忍弛,因此單獨開一篇專門講這塊內(nèi)容。

二考抄、 依賴引入

引入基礎(chǔ)的依賴包细疚,使用中間組件完成所需功能。


       <!-- 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>

由于組件之間存在交互關(guān)系川梅,因此將其全部放到一起疯兼。

三、高程操作

3.0 pre-step: 基礎(chǔ)環(huán)境配置

由于接下去的操作都需要依賴同一份數(shù)據(jù)源挑势,因此在最開始的時候镇防,需要先封裝關(guān)于數(shù)據(jù)源的相關(guān)操作。

3.0.1 基礎(chǔ)數(shù)據(jù)源初始化

3.0.1.1 GridCoverage2D創(chuàng)建

public void gridCoverage2D() throws IOException {
        //讀取基礎(chǔ)dem文件
        File file = new File(demFilePath);
        //加載文件所需基礎(chǔ)參數(shù)與投影參數(shù)
        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潮饱,獲得基礎(chǔ)GridCoverage2D 来氧。
        GridCoverage2D coverage = tifReader.read(null);
}

3.0.1.2 TIFFImage創(chuàng)建

    public TIFFImage tiffImage() {
        File file = new File(demFileMin);
        try {
            return TiffReader.readTiff(file);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

3.1 獲取指定經(jīng)緯度高程

通過獲取gridCoverage2D 的的投影信息,加載投影并使用經(jīng)緯度進行計算位置點高程

    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 獲取區(qū)域范圍極值

 @ApiImplicitParams({
            @ApiImplicitParam(name = "geometry", value = "飛行區(qū)域的數(shù)據(jù)", required = true, paramType = "GeoJSON"),
            @ApiImplicitParam(name = "geometry_type", value = "圖形數(shù)據(jù)格式(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
    /**
     * 獲取區(qū)域的高程點列表
     * @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);
    }


    /**
     * 計算數(shù)組極值
     *
     * @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 區(qū)域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給出一個基礎(chǔ)圖層,但這個基礎(chǔ)圖層使用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];
            //經(jīng)緯度轉(zhuǎn)點楔壤,X是經(jīng)度,表寬惯驼;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]);
                }
            }
            /* 更新參數(shù) */
            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();
        }
    }


    /**
     * 根據(jù)幾何模型進行影像切割
     *
     * @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);
    }

四、總結(jié)

總體來說祟牲,整個的實現(xiàn)流程基本上可以概括為[引入依賴]->[加載數(shù)據(jù)源]->[業(yè)務(wù)數(shù)據(jù)讀取]隙畜。讀取DEM的高程流程相對簡單,獲取范圍內(nèi)的高程極值操作也不復雜说贝,主要是需要算出范圍對應的格網(wǎng)议惰,以及獲取每個格網(wǎng)像元的高程值;較為復雜的是DEM的圖像導出乡恕,由于使用geotools導出的dem圖像存在一定的偏移與投影問題换淆,導致其他平臺軟件無法正常讀取dem的一些屬性信息,因此在操作上几颜,先使用geotools自定義的導出倍试,先輸出一份DEM,再使用正常平臺導出的dem文件蛋哭,通過讀取它的屬性信息县习,賦值給空的dem影像,再將像元的高程值寫入谆趾,最后輸出成一份符合規(guī)范的dem躁愿。此操作較為繁瑣,但基礎(chǔ)滿足功能所需沪蓬,此為拋磚引玉彤钟,如有更好的辦法實現(xiàn),望諸位不吝賜教跷叉,將感激不盡逸雹!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寇甸,一起剝皮案震驚了整個濱河市童社,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖壮池,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锭汛,死亡現(xiàn)場離奇詭異么夫,居然都是意外死亡棍苹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門日矫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赂弓,“玉大人,你說我怎么就攤上這事哪轿〖鹫梗” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵缔逛,是天一觀的道長。 經(jīng)常有香客問我姓惑,道長褐奴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任于毙,我火速辦了婚禮敦冬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唯沮。我一直安慰自己脖旱,他們只是感情好,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布介蛉。 她就那樣靜靜地躺著萌庆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪币旧。 梳的紋絲不亂的頭發(fā)上践险,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機與錄音吹菱,去河邊找鬼巍虫。 笑死,一個胖子當著我的面吹牛鳍刷,可吹牛的內(nèi)容都是我干的占遥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼输瓜,長吁一口氣:“原來是場噩夢啊……” “哼瓦胎!你這毒婦竟也來了芬萍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凛捏,失蹤者是張志新(化名)和其女友劉穎担忧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坯癣,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡瓶盛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了示罗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惩猫。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蚜点,靈堂內(nèi)的尸體忽然破棺而出轧房,到底是詐尸還是另有隱情,我是刑警寧澤绍绘,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布奶镶,位于F島的核電站,受9級特大地震影響陪拘,放射性物質(zhì)發(fā)生泄漏厂镇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一左刽、第九天 我趴在偏房一處隱蔽的房頂上張望捺信。 院中可真熱鬧,春花似錦欠痴、人聲如沸迄靠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掌挚。三九已至,卻和暖如春菩咨,著一層夾襖步出監(jiān)牢的瞬間疫诽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工旦委, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奇徒,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓缨硝,卻偏偏與公主長得像摩钙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子查辩,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 今天感恩節(jié)哎胖笛,感謝一直在我身邊的親朋好友网持。感恩相遇!感恩不離不棄长踊。 中午開了第一次的黨會功舀,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,567評論 0 11
  • 彩排完,天已黑
    劉凱書法閱讀 4,222評論 1 3
  • 表情是什么身弊,我認為表情就是表現(xiàn)出來的情緒辟汰。表情可以傳達很多信息。高興了當然就笑了阱佛,難過就哭了帖汞。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,299評論 2 7