¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |
| | | |
| | | } |