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