package com.se.nsl.service; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.se.nsl.config.PropertiesConfig; import com.se.nsl.domain.dto.Zarr2Tif; import com.se.nsl.domain.po.Rainfall; import com.se.nsl.domain.po.Simu; import com.se.nsl.domain.po.SimuData; import com.se.nsl.domain.vo.ConfigVo; import com.se.nsl.helper.ComHelper; import com.se.nsl.helper.GdalHelper; import com.se.nsl.helper.StringHelper; import com.se.nsl.helper.WebHelper; import lombok.SneakyThrows; 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.gdal.ogr.Geometry; import org.gdal.ogr.ogr; import org.gdal.osr.CoordinateTransformation; import org.gdal.osr.SpatialReference; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; @Slf4j @Service @SuppressWarnings("ALL") public class ResolveService { @Resource SimuService simuService; @Resource PropertiesConfig config; @Resource UwService uwService; @Resource TestService testService; Integer DIGIT = 1000000; SimpleDateFormat YYYYMDHM = new SimpleDateFormat("yyyy M d H m "); List MODES = new ArrayList<>(Arrays.asList("正态分布", "平均分布", "波动平均分布", "持续上升")); public int start(Simu simu) { Date now = new Date(); String date = StringHelper.YMDHMS2_FORMAT.format(now); // 将WKT转换为Geometry对象 Geometry geom = Geometry.CreateFromWkt(simu.getGeom()); if (geom.GetGeometryType() == ogr.wkbMultiPolygon) { geom = geom.GetGeometryRef(0); } double[] envelope = new double[4]; geom.GetEnvelope(envelope); SimuData data = JSON.parseObject(simu.getData(), SimuData.class); data.setInPath(date); data.setOutPath(date); data.setEnvelope(envelope); data.setEpsg(config.getEpsg()); simu.setData(JSON.toJSONString(data)); simu.setServiceName(date); simu.setStatus(1); // 0-创建仿真,1-预处理,2-分析中,10-完成,20-出错 simu.setUpdateTime(new Timestamp(now.getTime())); int rows = simuService.updateById(simu); if (rows > 0) { asyncCall(simu); } return rows; } private void asyncCall(Simu simu) { ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(new Runnable() { @Override @SneakyThrows public void run() { cope(simu); } }); executor.shutdown(); } private void cope(Simu simu) { try { SimuData data = JSONUtil.toBean(simu.getData(), SimuData.class); update(simu, 1, "初始化参数"); initArgs(simu, data); createRainfallFile(simu, data); update(simu, 2, "调用求解器"); callUwSolver(data); update(simu, 3, "调用Zarr转Tif"); callZarr2tif(data); update(simu, 4, "解析数据"); createNsl(data); update(simu, 10, "完成"); } catch (Exception ex) { log.error(ex.getMessage(), ex); update(simu, 20, ex.getMessage()); } } private void update(Simu simu, int status, String rs) { simu.setStatus(status); if (null != rs) simu.setResult(rs); simu.setUpdateTime(WebHelper.getCurrentTimestamp()); simuService.updateById(simu); } /** * 初始化参数 */ public void initArgs(Simu simu, SimuData data) throws IOException { String inPath = config.getInPath() + File.separator + data.getInPath(); createDir(inPath); createDir(inPath + File.separator + "depth"); createDir(inPath + File.separator + "velocity"); createDir(config.getOutPath() + File.separator + data.getOutPath()); Geometry geom = Geometry.CreateFromWkt(simu.getGeom()); if (geom.GetGeometryType() == ogr.wkbMultiPolygon) geom = geom.GetGeometryRef(0); SpatialReference dstSR = GdalHelper.createSpatialReference(config.getEpsg()); CoordinateTransformation ct = CoordinateTransformation.CreateCoordinateTransformation(GdalHelper.SR4326, dstSR); geom.Transform(ct); String wkt = geom.ExportToWkt(); String terrainFile = inPath + File.separator + config.getTerrainFile(); Dataset dsDem = gdal.Open(config.getSourceDem(), gdalconstConstants.GA_ReadOnly); ComHelper.Resample(dsDem, null, terrainFile, null, wkt, null, null); dsDem.delete(); String landuseFile = inPath + File.separator + config.getLanduseFile(); Dataset dsLanduse = gdal.Open(config.getSourceLanduse(), gdalconstConstants.GA_ReadOnly); ComHelper.Resample(dsLanduse, null, landuseFile, null, wkt, null, null); dsLanduse.delete(); } public void updateTif(Simu simu, SimuData data2) throws IOException { Dataset ds = gdal.Open(config.getSourceLanduse(), gdalconstConstants.GA_Update); // 以读写模式打开TIFF文件 Band band = ds.GetRasterBand(1); if (band.GetRasterDataType() != gdalconstConstants.GDT_Byte) { System.err.println("错误:非Byte类型数据"); ds.delete(); return; } int width = band.getXSize(); int height = band.getYSize(); // 读取Byte类型数据 byte[] data = new byte[width * height]; band.ReadRaster(0, 0, width, height, data); // 替换0值为8(注意Java的byte是有符号的,需处理0-255范围) for (int i = 0; i < data.length; i++) { if ((data[i] & 0xFF) == 0) { // 无符号比较 data[i] = (byte) 8; } } band.WriteRaster(0, 0, width, height, data); // 写回数据并保存 band.SetNoDataValue(8.0); // 设置新Nodata值 band.FlushCache(); // 强制写入更改 ds.delete(); } private void createDir(String path) { File f = new File(path); if (f.exists() && f.isDirectory()) { FileUtil.del(f); } f.mkdirs(); } public void createRainfallFile(Simu simu, SimuData data) throws Exception { List rainfalls = data.getRainfalls(); if (null == rainfalls || rainfalls.size() < 2) createRainfall(simu); String dat = config.getInPath() + File.separator + "Rainfalls" + File.separator + simu.getId() + ".dat"; String rainfallFile = config.getInPath() + File.separator + data.getInPath() + File.separator + "rainfall.dat"; if (new File(dat).exists()) { Files.copy(Paths.get(dat), Paths.get(rainfallFile), StandardCopyOption.REPLACE_EXISTING); return; } List list = new ArrayList<>(); list.add(config.getRainfallTitle()); double centerX = ComHelper.getMinVal((data.getMinx() + data.getMaxx()) / 2, DIGIT); double centerY = ComHelper.getMinVal((data.getMiny() + data.getMaxy()) / 2, DIGIT); String prefix = config.getRainfallSite() + " " + centerX + " " + centerY + " "; int unit = StringUtils.isEmpty(data.getIntensityUnit()) || "mm/h".equals(data.getIntensityUnit()) ? 60 : 5; int c = rainfalls.size() - 1; for (int i = 0; i < c; i++) { Rainfall r1 = rainfalls.get(i); Rainfall r2 = rainfalls.get(i + 1); list.addAll(calcRainfall(r1, r2, prefix, unit)); } //list.add(prefix + YYYYMDHM.format(rainfalls.get(c).getTime()) + getMinVal(rainfalls.get(c).getIntensity() / unit, DIGIT)); list.add(String.format("%s%s%f", prefix, YYYYMDHM.format(rainfalls.get(c).getTime()), getMinVal(rainfalls.get(c).getIntensity() / unit, DIGIT))); list.add(0, "1 " + (list.size() - 1)); Files.write(Paths.get(rainfallFile), list, StandardCharsets.UTF_8); } public static double getMinVal(double val, double radix) { return ((long) Math.floor(val * radix)) / radix; } // beijing 116.0 40.0 2025 1 1 0 13 1.666666 private List calcRainfall(Rainfall r1, Rainfall r2, String prefix, int unit) { long mins = Math.abs(r2.getTime().getTime() - r1.getTime().getTime()) / (1000 * 60); // 计算分钟数 double diff = (r2.getIntensity() - r1.getIntensity()) / mins / unit; //ComHelper.getMinVal((r1.getIntensity() - r2.getIntensity()) / mins / unit, DIGIT); Calendar cal = Calendar.getInstance(); cal.setTime(r1.getTime()); List list = new ArrayList<>(); double intensity = ComHelper.getMinVal(r1.getIntensity() / unit, DIGIT); for (int i = 0; i < mins; i++) { //list.add(prefix + YYYYMDHM.format(cal.getTime()) + getMinVal((intensity + diff * i), DIGIT)); list.add(String.format("%s%s%f", prefix, YYYYMDHM.format(cal.getTime()), getMinVal((intensity + diff * i), DIGIT))); cal.add(Calendar.MINUTE, 1); } return list; } /** * 调用UWSolver */ public String callUwSolver(SimuData data) throws Exception { File uwBat = new File(config.getUwSolverBat()); int duration = 3600 * data.getDuration(); // 秒数 if (null != data.getRainfalls() && data.getRainfalls().size() > 1) { duration = (int) (Math.abs(data.getRainfalls().get(data.getRainfalls().size() - 1).getTime().getTime() - data.getRainfalls().get(0).getTime().getTime()) / 60); } String inPath = config.getInPath() + File.separator + data.getInPath(); String terrainFile = inPath + File.separator + config.getTerrainFile(); String landuseFile = inPath + File.separator + config.getLanduseFile(); String rainfallFile = (inPath + File.separator + "rainfall.dat"); String saveName = inPath + File.separator + "result.zarr"; ConfigVo vo = new ConfigVo(terrainFile, landuseFile, terrainFile, rainfallFile, saveName, duration, config.getSaveFrames()); String configFile = config.getInPath() + File.separator + data.getInPath() + File.separator + data.getInPath() + ".json"; ComHelper.writeJson(configFile, JSON.toJSONString(vo)); String cmd = String.format("%s \"%s\"", config.getUwSolverBat(), configFile); return callBat2(cmd); } /** * 调用zarr2tif */ public String callZarr2tif(SimuData data) throws Exception { String inPath = config.getInPath() + File.separator + data.getInPath(); String zarrFile = inPath + File.separator + "result.zarr"; String geotiffDir = inPath + File.separator + "depth"; String terrainFile = inPath + File.separator + config.getTerrainFile(); String jsonPath = inPath + File.separator + "zarr2tif.json"; Zarr2Tif zarr2Tif = new Zarr2Tif(zarrFile, geotiffDir, terrainFile, data.getStartTime()); ComHelper.writeJson(jsonPath, JSON.toJSONString(zarr2Tif)); String cmd = String.format("%s \"%s\"", config.getZarr2tifBat(), jsonPath); return callBat2(cmd); } private String callBat2(String cmd) { try { ProcessBuilder pb = new ProcessBuilder("cmd", "/c", cmd); pb.redirectErrorStream(true); // 合并错误流到标准输出 Process process = pb.start(); process.getOutputStream().close(); //StringBuilder sb = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); //sb.append(line); } } int exitCode = process.waitFor(); return "ok"; // sb.toString(); } catch (Exception ex) { log.error(ex.getMessage(), ex); return null; } } /*private String callZarr2tif(SimuData data) throws Exception { File uwBat = new File(config.getUwSolverBat()); String zarrFile = uwBat.getParent() + File.separator + "result.zarr"; String inPath = config.getInPath() + File.separator + data.getInPath(); String terrainFile = inPath + File.separator + config.getTerrainFile(); String waterPath = inPath + File.separator + "depth"; String cmd = String.format("%s \"%s\" \"%s\" \"%s\" \"%s\"", config.getZarr2tifBat(), "depth", zarrFile, terrainFile, waterPath); return callBat(cmd); }*/ private String callBat(String cmd) { try { ProcessBuilder pb = new ProcessBuilder("cmd", "/c", cmd); pb.redirectErrorStream(true); // 合并错误流到标准输出 Process process = pb.start(); process.getOutputStream().close(); /*StringBuilder sb = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); sb.append(line); } }*/ int exitCode = process.waitFor(); return "ok"; // sb.toString(); } catch (Exception ex) { log.error(ex.getMessage(), ex); return null; } } private void createNsl(SimuData data) throws Exception { /*String inPath = config.getInPath() + File.separator + data.getInPath() + File.separator + "depth"; procTifs(inPath, inPath, data.getStartTime());*/ testService.test(data); } private void procTifs(String tifPath, String outPath, Date startTime) { SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); Calendar calendar = Calendar.getInstance(); calendar.setTime(startTime); for (File file : new File(tifPath).listFiles()) { if (!file.exists() || !file.isDirectory()) continue; File tif = new File(tifPath + "\\" + file.getName() + File.separator + "depth.tif"); if (!tif.exists() || tif.isDirectory()) continue; calendar.add(Calendar.SECOND, 1); String newName = df.format(calendar.getTime()); String newFile = outPath + File.separator + newName + ".tif"; System.out.println(newFile); tif.renameTo(new File(newFile)); file.delete(); } } public String createRainfallCsv(String csvPath, String mode, double total, double intensity, int hours) { // python 脚本名.py <参数1-csv文件名> <参数2-降雨模式:正态分布|平均分布|波动平均分布|持续上升> <参数3-降雨总量> <参数4-最大雨强> <参数5-降雨时间(分钟)> String cmd = String.format("%s \"%s\" \"%s\" %f %f %d", config.getCreateRainfall(), csvPath, mode, total, intensity, hours * 60); return callBat(cmd); } public void createRainfall(Simu simu) throws Exception { SimuData data = JSON.parseObject(simu.getData(), SimuData.class); if (null == data.getMode() || MODES.contains(data.getMode())) data.setMode(MODES.get(0)); if (StringUtils.isEmpty(data.getIntensityUnit())) data.setIntensityUnit("mm/h"); Geometry geom = Geometry.CreateFromWkt(simu.getGeom()); if (geom.GetGeometryType() == ogr.wkbMultiPolygon) geom = geom.GetGeometryRef(0); double[] envelope = new double[4]; geom.GetEnvelope(envelope); data.setEnvelope(envelope); data.setEpsg(config.getEpsg()); String basePath = config.getInPath() + File.separator + "Rainfalls"; if (!new File(basePath).exists()) new File(basePath).mkdirs(); if (null == simu.getCreateTime()) simu.setCreateTime(new Timestamp(new Date().getTime())); String csvPath = basePath + File.separator + simu.getId() + ".csv"; int unit = StringUtils.isEmpty(data.getIntensityUnit()) || "mm/h".equals(data.getIntensityUnit()) ? 60 : 5; createRainfallCsv(csvPath, data.getMode(), data.getTotal(), data.getIntensity() / unit, data.getDuration()); List list = getValues(csvPath); if (!CollUtil.isEmpty(list)) { data.setRainfalls(new ArrayList<>()); setRainfalls(simu, data, list); } simu.setData(JSON.toJSONString(data)); } private List getValues(String csvPath) throws Exception { if (!new File(csvPath).exists()) return null; List list = Files.readAllLines(Paths.get(csvPath)); list.remove(0); //list.remove(list.size() - 1); return list.stream() .map(s -> new BigDecimal(s).setScale(6, RoundingMode.HALF_DOWN).doubleValue()) .collect(Collectors.toList()); } private void setRainfalls(Simu simu, SimuData data, List vals) throws Exception { String basePath = config.getInPath() + File.separator + "Rainfalls"; String dat = basePath + File.separator + simu.getId() + ".dat"; Calendar cal = Calendar.getInstance(); cal.setTime(data.getStartTime()); List list = new ArrayList<>(); list.add(config.getRainfallTitle()); double centerX = ComHelper.getMinVal((data.getMinx() + data.getMaxx()) / 2, DIGIT); double centerY = ComHelper.getMinVal((data.getMiny() + data.getMaxy()) / 2, DIGIT); String prefix = config.getRainfallSite() + " " + centerX + " " + centerY + " "; Double total = 0.0; for (int i = 0, c = vals.size(); i < c; i++) { total += vals.get(i); if (i % 15 == 0) { data.getRainfalls().add(new Rainfall(cal.getTime(), vals.get(i), total)); } list.add(String.format("%s%s%f", prefix, YYYYMDHM.format(cal.getTime()), vals.get(i))); cal.add(Calendar.MINUTE, 1); } list.add(0, "1 " + (list.size() - 1)); Files.write(Paths.get(dat), list, StandardCharsets.UTF_8); } }