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 crossSectionAnalysis(String serviceName, double[] startPoint, double[] endPoint) { List 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 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 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; } } }