dcb
2025-07-09 e53252b99e7b49b435b7a6ee3eab21ae1bd7a055
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
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;
        }
 
    }
 
}