From e53252b99e7b49b435b7a6ee3eab21ae1bd7a055 Mon Sep 17 00:00:00 2001
From: dcb <xgybdcb@163.com>
Date: 星期三, 09 七月 2025 16:11:40 +0800
Subject: [PATCH] 断面分析功能实现

---
 src/main/java/com/se/nsl/controller/SimuController.java            |   35 +++-
 src/main/java/com/se/nsl/utils/SolverTifUtil.java                  |    2 
 src/main/java/com/se/nsl/config/AsyncExecutor.java                 |    2 
 src/main/java/com/se/nsl/service/CrossSectionAnalysisService.java  |  317 +++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/se/nsl/domain/vo/CrossSectionAnalysisResult.java |   49 +++++++
 src/main/resources/application-dev.yml                             |   11 
 6 files changed, 400 insertions(+), 16 deletions(-)

diff --git a/src/main/java/com/se/nsl/config/AsyncExecutor.java b/src/main/java/com/se/nsl/config/AsyncExecutor.java
index 5bebab8..9570c0a 100644
--- a/src/main/java/com/se/nsl/config/AsyncExecutor.java
+++ b/src/main/java/com/se/nsl/config/AsyncExecutor.java
@@ -14,7 +14,7 @@
         executor.setCorePoolSize(5);
         executor.setMaxPoolSize(10);
         executor.setQueueCapacity(25);
-        executor.setThreadNamePrefix("RealTimeExecutor-");
+        executor.setThreadNamePrefix("RTExecutor-");
         executor.initialize();
         return executor;
     }
diff --git a/src/main/java/com/se/nsl/controller/SimuController.java b/src/main/java/com/se/nsl/controller/SimuController.java
index f289da9..9ca589b 100644
--- a/src/main/java/com/se/nsl/controller/SimuController.java
+++ b/src/main/java/com/se/nsl/controller/SimuController.java
@@ -10,6 +10,7 @@
 import com.se.nsl.utils.SimulateType;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Parameter;
 import lombok.extern.slf4j.Slf4j;
 import org.gdal.ogr.Geometry;
 import org.gdal.ogr.ogr;
@@ -205,24 +206,40 @@
 
     @ApiOperation(value = "crossSection")
     @GetMapping("/crossSection")
-    public R<Object> crossSection(String serviceName, double[] startPoint, double[] endPoint) {
+    public R<Object> crossSection(@Parameter(description = "鏂规id锛岀ず渚嬶細50") Integer id,
+                                  @Parameter(description = "鏃堕棿鎴筹紝绀轰緥锛�1751552400000") Long time,
+                                  @Parameter(description = "璧风偣鍧愭爣锛岀ず渚嬶細116.59049537485063,40.564178548127686") String startPoint,
+                                  @Parameter(description = "缁堢偣鍧愭爣锛岀ず渚嬶細116.5901406492509,40.56499045715429") String endPoint) {
+        if (null == id || id < 1) return clientError("id涓嶈兘涓虹┖");
+        Simu simu = simuService.selectById(id);
+        if (simu == null) {
+            return clientError("鎵句笉鍒板搴旂殑鏈嶅姟");
+        }
+        String serviceName = simu.getServiceName();
         if (serviceName == null) {
-            return clientError("鏈嶅姟鍚嶄笉鑳戒负绌�");
+            return fail("鎵句笉鍒板搴旂殑鏈嶅姟");
         }
         if (startPoint == null) {
             return clientError("璧风偣涓嶈兘涓虹┖");
         }
-        if (startPoint.length < 2) {
-            return clientError("璧风偣鑷冲皯鍖呭惈x,y涓や釜鍊�");
-        }
         if (endPoint == null) {
             return clientError("缁堢偣涓嶈兘涓虹┖");
         }
-        if (endPoint.length < 2) {
-            return clientError("缁堢偣鑷冲皯鍖呭惈x,y涓や釜鍊�");
+        String[] sp = startPoint.split(",");
+        double[] s = new double[] {Double.parseDouble(sp[0]), Double.parseDouble(sp[1])};
+        String[] ep = endPoint.split(",");
+        double[] e = new double[] {Double.parseDouble(ep[0]), Double.parseDouble(ep[1])};
+        try {
+            if (time == null) {
+                List<CrossSectionAnalysisResult> result = scas.crossSectionAnalysis(serviceName, s, e);
+                return success(result, result.size());
+            } else {
+                CrossSectionAnalysisResult res = scas.crossSectionAnalysis(serviceName, s, e, time);
+                return success(res, 1);
+            }
+        } catch (IllegalArgumentException ex) {
+            return fail(ex.getMessage(), null);
         }
-        List<CrossSectionAnalysisResult> result = scas.crossSectionAnalysis(serviceName, startPoint, endPoint);
-        return success(result);
     }
 
     @ApiOperation(value = "stop")
diff --git a/src/main/java/com/se/nsl/domain/vo/CrossSectionAnalysisResult.java b/src/main/java/com/se/nsl/domain/vo/CrossSectionAnalysisResult.java
new file mode 100644
index 0000000..ac97100
--- /dev/null
+++ b/src/main/java/com/se/nsl/domain/vo/CrossSectionAnalysisResult.java
@@ -0,0 +1,49 @@
+package com.se.nsl.domain.vo;
+
+public class CrossSectionAnalysisResult {
+    private double depth;
+    private double velocity; //娴侀��
+    private double flowRate; //娴侀噺
+    private long time; //鏃堕棿
+
+    public CrossSectionAnalysisResult() {}
+
+    public CrossSectionAnalysisResult(double depth,double flowRate, double velocity, long time) {
+        this.depth = depth;
+        this.flowRate = flowRate;
+        this.velocity = velocity;
+        this.time = time;
+    }
+
+    public double getDepth() {
+        return depth;
+    }
+
+    public void setDepth(double depth) {
+        this.depth = depth;
+    }
+
+    public double getFlowRate() {
+        return flowRate;
+    }
+
+    public void setFlowRate(double flowRate) {
+        this.flowRate = flowRate;
+    }
+
+    public double getVelocity() {
+        return velocity;
+    }
+
+    public void setVelocity(double velocity) {
+        this.velocity = velocity;
+    }
+
+    public long getTime() {
+        return time;
+    }
+
+    public void setTime(long time) {
+        this.time = time;
+    }
+}
diff --git a/src/main/java/com/se/nsl/service/CrossSectionAnalysisService.java b/src/main/java/com/se/nsl/service/CrossSectionAnalysisService.java
new file mode 100644
index 0000000..e30984f
--- /dev/null
+++ b/src/main/java/com/se/nsl/service/CrossSectionAnalysisService.java
@@ -0,0 +1,317 @@
+package com.se.nsl.service;
+
+import com.se.nsl.config.PropertiesConfig;
+import com.se.nsl.domain.vo.CrossSectionAnalysisResult;
+import com.se.nsl.domain.vo.SimuResult;
+import com.se.nsl.utils.CoordinateTransformer;
+import com.se.nsl.utils.SolverTifUtil;
+import com.se.nsl.utils.TimeFormatUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.gdal.gdal.Band;
+import org.gdal.gdal.Dataset;
+import org.gdal.gdal.gdal;
+import org.gdal.gdalconst.gdalconstConstants;
+import org.locationtech.jts.geom.*;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class CrossSectionAnalysisService {
+
+    public static final String TIF = ".tif";
+    public static final String YYYY_MM_DD_HH_MM_SS = "yyyyMMddHHmmss";
+    private static final double SAMPLING_DISTANCE = 1;
+    @Resource
+    PropertiesConfig config;
+
+    public List<CrossSectionAnalysisResult> crossSectionAnalysis(String serviceName, double[] startPoint, double[] endPoint) {
+        List<CrossSectionAnalysisResult> results = new ArrayList<>();
+        //鎵惧埌鎸囧畾鐨勬湇鍔$洰褰曠殑鍦板舰鏂囦欢
+        String inPath = config.getInPath();
+        File serviceNameDir = new File(inPath, serviceName);
+        if (!serviceNameDir.exists()) {
+            throw new IllegalArgumentException(serviceName + "涓嶅瓨鍦�");
+        }
+        //鏍规嵁璧风偣鍜岀粓鐐圭敾鏉$嚎锛屼笌鍦板舰鏋勫缓涓�涓杈瑰舰
+        File dem = new File(serviceNameDir, "DEM.tif");
+        ChannelCrossSectionExtractor extractor = new ChannelCrossSectionExtractor(dem.getAbsolutePath());
+        Point s = coordinateTransform(startPoint);
+        Point e = coordinateTransform(endPoint);
+        extractor.extractCrossSection(s, e, SAMPLING_DISTANCE);
+        File depthDir = new File(serviceNameDir, "depth");
+        File[] files = depthDir.listFiles();
+        if (files == null) return Collections.emptyList();
+        for (File tif : files) {
+            String name = tif.getName();
+            if (!name.endsWith(TIF)) continue;
+            CrossSectionAnalysisResult ar = getCrossSectionAnalysisResult(tif, extractor);
+            if (ar != null) {
+                results.add(ar);
+            }
+        }
+        extractor.destroy();
+        return results;
+    }
+
+    private CrossSectionAnalysisResult getCrossSectionAnalysisResult(File tif, ChannelCrossSectionExtractor extractor) {
+        Point position = extractor.getMaxDepthPosition();
+        if (position == null) return null;
+        SimuResult sr = querySimuResult(tif, position);
+        //鏌ヨ瀵瑰簲鐐圭殑姘存繁
+        double waterDepth = sr.getDepth();
+        Polygon polygon = extractor.generateWettedPolygon(waterDepth);
+        double area = calculateArea(polygon);
+        //鏌ヨ瀵瑰簲鐐圭殑娴侀��
+        double v = sr.getVelocity();
+        //璁$畻鍑烘鏃剁殑姘存祦閲�
+        double flowRate = v * area;
+        String name = tif.getName();
+        String prefix = name.replace(TIF, "");
+        long time = TimeFormatUtil.toMillis(prefix, YYYY_MM_DD_HH_MM_SS);
+        return new CrossSectionAnalysisResult(waterDepth, flowRate, v, time);
+    }
+
+    public CrossSectionAnalysisResult crossSectionAnalysis(String serviceName, double[] startPoint, double[] endPoint, long time) {
+        //鎵惧埌鎸囧畾鐨勬湇鍔$洰褰曠殑鍦板舰鏂囦欢
+        String inPath = config.getInPath();
+        File serviceNameDir = new File(inPath, serviceName);
+        if (!serviceNameDir.exists()) {
+            throw new IllegalArgumentException(serviceName + "涓嶅瓨鍦�");
+        }
+        //鏍规嵁璧风偣鍜岀粓鐐圭敾鏉$嚎锛屼笌鍦板舰鏋勫缓涓�涓杈瑰舰
+        File dem = new File(serviceNameDir, "DEM.tif");
+        ChannelCrossSectionExtractor extractor = new ChannelCrossSectionExtractor(dem.getAbsolutePath());
+        Point s = coordinateTransform(startPoint);
+        Point e = coordinateTransform(endPoint);
+        extractor.extractCrossSection(s, e, SAMPLING_DISTANCE);
+        String prefix = TimeFormatUtil.formatTime(time, YYYY_MM_DD_HH_MM_SS);
+        File tif = Paths.get(inPath, serviceName, "depth", prefix + ".tif").toFile();
+        CrossSectionAnalysisResult ar = getCrossSectionAnalysisResult(tif, extractor);
+        extractor.destroy();
+        return ar;
+    }
+
+    private Point coordinateTransform(double[] startPoint) {
+        double lon = startPoint[0];
+        double lat = startPoint[1];
+        double[] xy = CoordinateTransformer.transform(4326, config.getEpsg(), lon, lat);
+        return new Point(xy[0], xy[1]);
+    }
+
+    private SimuResult querySimuResult(File tif, Point pos) {
+        double[] xy = new double[]{pos.x, pos.y};
+        return SolverTifUtil.getSimuResult(tif, xy);
+    }
+
+
+    private double calculateArea(Geometry geometry) {
+        if (geometry == null) return 0;
+        return geometry.getArea();
+    }
+
+
+    private static class ChannelCrossSectionExtractor {
+        private final GeometryFactory geometryFactory;
+        private final Dataset dataset;
+        private final List<CrossSectionPoint> crossSection;
+        private Point maxDepthPosition; //鏈�娣变綅缃殑鍧愭爣
+        private double minElevation = Double.MAX_VALUE;
+        private double maxElevation = Double.MIN_VALUE;
+
+        public ChannelCrossSectionExtractor(String tifPath) {
+            geometryFactory = new GeometryFactory();
+            // 璇诲彇TIFF鏂囦欢
+            File file = new File(tifPath);
+            this.dataset = gdal.Open(file.getAbsolutePath(), gdalconstConstants.GA_ReadOnly);
+            crossSection = new ArrayList<>();
+        }
+
+        public void destroy() {
+            if (dataset != null) {
+                dataset.delete();
+            }
+        }
+
+        /**
+         * 鎻愬彇涓ょ偣涔嬮棿鐨勫湴褰㈡埅闈�
+         */
+        public void extractCrossSection(Point startPoint, Point endPoint, double samplingDistance) {
+            // 璁$畻涓ょ偣涔嬮棿鐨勮窛绂�
+            double distance = Math.sqrt(Math.pow(endPoint.x - startPoint.x, 2) + Math.pow(endPoint.y - startPoint.y, 2));
+            // 璁$畻閲囨牱鐐规暟閲�
+            int numPoints = (int) Math.ceil(distance / samplingDistance) + 1;
+
+            for (int i = 0; i < numPoints; i++) {
+                // 娌跨洿绾挎彃鍊艰绠楅噰鏍风偣鍧愭爣
+                double fraction = (double) i / (numPoints - 1);
+                double x = startPoint.x + fraction * (endPoint.x - startPoint.x);
+                double y = startPoint.y + fraction * (endPoint.y - startPoint.y);
+                // 璁$畻璺濈璧风偣鐨勬按骞宠窛绂�
+                double horizontalDistance = fraction * distance;
+                // 鑾峰彇璇ョ偣鐨勯珮绋嬪��
+                double elevation = getElevation(x, y);
+                minElevation = Math.min(minElevation, elevation);
+                maxElevation = Math.max(maxElevation, elevation);
+                crossSection.add(new CrossSectionPoint(horizontalDistance, x, y, elevation));
+            }
+        }
+
+        /**
+         * 鑾峰彇鎸囧畾浣嶇疆鐨勯珮绋嬪��
+         */
+        private double getElevation(double x, double y) {
+            double[] geoTransform = dataset.GetGeoTransform();
+            //璁$畻鏍呮牸琛屽垪鍙�
+            int col = (int) ((x - geoTransform[0]) / geoTransform[1]);
+            int row = (int) ((geoTransform[3] - y) / Math.abs(geoTransform[5]));
+            Band band = dataset.GetRasterBand(1);
+            float[] values = new float[1];
+            band.ReadRaster(col, row, 1, 1, values);
+            return values[0];
+        }
+
+        /**
+         * 鐢熸垚杩囨按鏂潰澶氳竟褰�
+         */
+        private Polygon generateWettedPolygon(double waterLevel) {
+            if (crossSection.isEmpty()) {
+                return null;
+            }
+            // 鍒涘缓鍦板舰鎴潰绾�
+            Coordinate[] terrainCoords = new Coordinate[crossSection.size()];
+            for (int i = 0; i < crossSection.size(); i++) {
+                CrossSectionPoint point = crossSection.get(i);
+                double elevation = point.getElevation();
+                terrainCoords[i] = new Coordinate(point.getDistance(), elevation);
+//                log.info("{},{}", point.getDistance(), point.getElevation());
+            }
+            LineString terrainLine = geometryFactory.createLineString(terrainCoords);
+//            log.info(terrainLine.toString());
+            // 鍒涘缓姘翠綅绾�
+            CrossSectionPoint first = crossSection.get(0);
+            CrossSectionPoint last = crossSection.get(crossSection.size() - 1);
+            double minDistance = first.getDistance();
+            double maxDistance = last.getDistance();
+            Coordinate[] waterCoords = new Coordinate[2];
+            if (maxElevation - minElevation > waterLevel) {
+                waterLevel = minElevation + waterLevel;
+            } else {
+                waterLevel = Math.min(first.getElevation(), last.getElevation());
+            }
+//            log.info("waterLevle:{}", waterLevel);
+            waterCoords[0] = new Coordinate(minDistance, waterLevel);
+            waterCoords[1] = new Coordinate(maxDistance, waterLevel);
+            LineString waterLine = geometryFactory.createLineString(waterCoords);
+//            log.info(waterLine.toString());
+            // 璁$畻姘翠綅绾夸笌鍦板舰鐨勪氦鐐�
+            Geometry intersections = terrainLine.intersection(waterLine);
+            if (intersections.isEmpty()) {
+                return null; // 姘翠綅浣庝簬鍦板舰锛屾棤姘�
+            }
+            // 鑾峰彇鏈�澶栦晶鐨勪袱涓氦鐐逛綔涓烘渤宀�
+            Coordinate leftBank;
+            Coordinate rightBank;
+            if (intersections instanceof org.locationtech.jts.geom.Point) {
+                return null; // 鐗规畩鎯呭喌锛氭按浣嶅垰濂戒笌鍦板舰鐩稿垏浜庝竴鐐�
+            } else if (intersections instanceof MultiPoint) {
+                MultiPoint multiPoint = (MultiPoint) intersections;
+                int num = multiPoint.getNumGeometries();
+                if (num >= 2) {
+                    leftBank = multiPoint.getGeometryN(0).getCoordinate();
+                    rightBank = multiPoint.getGeometryN(num - 1).getCoordinate();
+                } else {
+                    return null;
+                }
+            } else {
+                return null;
+            }
+
+            // 鎻愬彇姘撮潰浠ヤ笅鐨勫湴褰㈢偣
+            double finalWaterLevel = waterLevel;
+            List<Coordinate> wettedCoords = crossSection.stream()
+                    .filter(c -> c.getElevation() < finalWaterLevel)
+                    .map(s -> new Coordinate(s.getDistance(), s.getElevation()))
+                    .collect(Collectors.toList());
+            wettedCoords.add(0, leftBank);
+            wettedCoords.add(rightBank);
+            // 闂悎澶氳竟褰�
+            if (wettedCoords.size() >= 3) {
+                // 纭繚澶氳竟褰㈤棴鍚堬紙棣栧熬鐩歌繛锛�
+                if (!wettedCoords.get(0).equals2D(wettedCoords.get(wettedCoords.size() - 1))) {
+                    wettedCoords.add(new Coordinate(wettedCoords.get(0)));
+                }
+                return geometryFactory.createPolygon(wettedCoords.toArray(new Coordinate[0]));
+            } else {
+                return null; // 鏃犳硶褰㈡垚鏈夋晥澶氳竟褰�
+            }
+        }
+
+        public Point getMaxDepthPosition() {
+            if (maxDepthPosition != null) {
+                return maxDepthPosition;
+            } else {
+                for (CrossSectionPoint csp : crossSection) {
+                    double elevation = csp.getElevation();
+                    if (Math.abs(elevation - minElevation) < 1e-15) {
+                        maxDepthPosition = new Point(csp.getX(), csp.getY());
+                        return maxDepthPosition;
+                    }
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * 鎴潰鐐圭被
+     */
+    private static class CrossSectionPoint {
+        private final double distance; // 璺濈璧风偣鐨勬按骞宠窛绂�
+        private final double x;
+        private final double y;
+        private final double elevation; // 楂樼▼
+
+        public CrossSectionPoint(double distance, double x, double y, double elevation) {
+            this.distance = distance;
+            this.x = x;
+            this.y = y;
+            this.elevation = elevation;
+        }
+
+        public double getDistance() {
+            return distance;
+        }
+
+        public double getX() {
+            return x;
+        }
+
+        public double getY() {
+            return y;
+        }
+
+        public double getElevation() {
+            return elevation;
+        }
+    }
+
+    private static class Point {
+        public double x;
+        public double y;
+
+        public Point(double x, double y) {
+            this.x = x;
+            this.y = y;
+        }
+
+    }
+
+}
diff --git a/src/main/java/com/se/nsl/utils/SolverTifUtil.java b/src/main/java/com/se/nsl/utils/SolverTifUtil.java
index f9f4adc..0179bca 100644
--- a/src/main/java/com/se/nsl/utils/SolverTifUtil.java
+++ b/src/main/java/com/se/nsl/utils/SolverTifUtil.java
@@ -60,7 +60,7 @@
         return readPixelValue(cr.dataset, cr.col, cr.row, band);
     }
 
-    private static ColumnRow getColumnRow(File tifFile, double x, double y) {
+    public static ColumnRow getColumnRow(File tifFile, double x, double y) {
         Dataset dataset = gdal.Open(tifFile.getAbsolutePath(), gdalconstConstants.GA_ReadOnly);
         // 鑾峰彇鍦扮悊鍙樻崲鍙傛暟锛�6鍏冪礌鏁扮粍锛�
         // [0]: 宸︿笂瑙扻鍧愭爣, [1]: 鍍忓厓瀹藉害, [2]: X鏂瑰悜鏃嬭浆,
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 4212d20..c335c44 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -149,8 +149,8 @@
   rainfallSite: beijing
   epsg: 4548
 #  saveFrames: 3
-  #鐢熸垚甯ф暟鐨勯棿闅旀椂闂达紝鍗曚綅鏄垎閽燂紝璁剧疆涓�5琛ㄧず姣忛殧5鍒嗛挓鐢熸垚涓�甯�
-  saveFrameInterval: 20
+  #鐢熸垚甯ф暟鐨勯棿闅旀椂闂达紝鍗曚綅鏄锛岃缃负30琛ㄧず姣忛殧30绉掔敓鎴愪竴甯�
+  saveFrameInterval: 600
   saveFilter: 0.015
   evaporation: 0.27
   # 鍦熷湴鍒╃敤锛�1-Cropland,2-Forest,3-Shrub,4-Grassland,5-Water,6-Snow/Ice,7-Barren,8-Impervious,9-Wetland
@@ -172,9 +172,10 @@
   #tif涓殑楂樼▼鏂囦欢鍚嶈鍚湁dem锛屽湡鍦板埄鐢ㄨ鍚湁landuse锛岀珯鐐硅鍚湁station锛屼笉闄愬埗澶у皬鍐欙紝鏂囧瓧灏介噺鐢ㄨ嫳鏂囧瓧姣嶈〃杈�
   keyDitch: D:\other\simu\CudaUWSolver-2.2.1\KeyDitch
 realtime-simulate-config:
-  url: http://192.168.56.106:9522/ylclyPacket/getData
-  token: YjhhYjAwOWFhMjk1MTM1ZDA0NGU3ZWZlMDQzMzUzZDE1MGJmY2Q4ZWEyYjliNjQzZjcwMjhlNDY0ZjAxNWZjOTZmNzMwYmNmZDA2YmVmNTIzNjU0ZDgzODRjYTUxYTM1
-  realTimeInterval: 5
+  url: http://192.168.56.106:9533/ylclyPacket/getData
+  token: NmNmNWZlMThmNTExYzEyMjEwMzNlMGViYjFiN2Y2MmEwMzkwNjA2MmFlZjYzMzU0MzE4YTIzMDUyZTM2MzU5ZWJjMDllNTYwZDNiN2JjZTU5YTFhNjg5Y2IwZGRlODRh
+  #瀹炴椂妯℃嫙姣忓抚鐨勯棿闅旓紝鍗曚綅绉�
+  frameInterval: 5
   #璇锋眰闆ㄩ噺璁℃暟鎹椂锛屾椂闂磋寖鍥寸浉宸灏戝垎閽�
   requestOffsetMinutes: 15
   #寰�鍓嶅亸绉诲灏戝ぉ锛屼负浜嗘柟渚垮疄鏃舵ā鎷熶互鍓嶇殑闄嶉洦鎯呭喌,璁剧疆涓�0琛ㄧず锛屼粠褰撳墠鏃堕棿寮�濮嬭绠�

--
Gitblit v1.9.3