dcb
2025-07-07 97629da6a002b534da10f828187fd6cd65941aa4
src/main/java/com/se/nsl/service/ResolveService.java
@@ -1,22 +1,53 @@
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.fasterxml.jackson.databind.ObjectMapper;
import com.se.nsl.config.PropertiesConfig;
import com.se.nsl.domain.po.DataPo;
import com.se.nsl.domain.dto.*;
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 com.se.nsl.utils.AreaType;
import com.se.nsl.utils.RainFallUnit;
import com.se.nsl.utils.SimulateType;
import com.se.nsl.utils.TimeFormatUtil;
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.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.sql.Timestamp;
import java.util.Date;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@Slf4j
@Service
@@ -28,15 +59,39 @@
    @Resource
    PropertiesConfig config;
    @Resource
    UwService uwService;
    @Resource
    TestService testService;
    private ObjectMapper mapper = new ObjectMapper();
    Integer DIGIT = 1000000;
    SimpleDateFormat YYYYMDHM = new SimpleDateFormat("yyyy M d H m ");
    List<String> 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.setEpsg(4548);
        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()));
@@ -63,18 +118,23 @@
    private void cope(Simu simu) {
        try {
            DataPo data = JSONUtil.toBean(simu.getData(), DataPo.class);
            SimuData data = JSONUtil.toBean(simu.getData(), SimuData.class);
            //update(simu, 2, null);
            //gedbService.copeVectors(token, data, db);
            update(simu, 1, "初始化参数");
            initArgs(simu, data);
            createRainfallFile(simu, data);
            //update(simu, 3, null);
            //gedbService.copeDem(token, data);
            update(simu, 2, "调用求解器");
            callUwSolver(simu, data);
            //update(simu, 4, null);
            //uwService.createRainFile(data);
            update(simu, 3, "调用Zarr转Tif");
            callZarr2tif(data);
            update(simu, 4, "解析数据");
            createNsl(data);
            update(simu, 10, "完成");
            log.info("模拟完成");
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            update(simu, 20, ex.getMessage());
@@ -88,4 +148,387 @@
        simuService.updateById(simu);
    }
    /**
     * 初始化参数
     */
    public void initArgs(Simu simu, SimuData data) {
        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());
        Short areaType = simu.getAreaType();
        AreaType at = AreaType.of(areaType);
        String terrainTif = config.getSourceDem();
        String landuseTif = config.getSourceLanduse();
        String stationTif = null;
        if (at == AreaType.KEY_DITCH) {
            File keyDitchDir = new File(config.getKeyDitch());
            String areaName = simu.getAreaName();
            Optional<File> first = Arrays.stream(keyDitchDir.listFiles()).filter(f -> f.getName().equals(areaName)).findFirst();
            if (first.isPresent()) {
                File targetTifDir = first.get();
                File[] files = targetTifDir.listFiles();
                for (File file : files) {
                    String name = file.getName();
                    if (name.toLowerCase().contains("dem")) {
                        terrainTif = file.getAbsolutePath();
                    }
                    if (name.toLowerCase().contains("landuse")) {
                        landuseTif = file.getAbsolutePath();
                    }
                    if (name.toLowerCase().contains("station")) {
                        stationTif = file.getAbsolutePath();
                    }
                }
            }
        }
        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(terrainTif, gdalconstConstants.GA_ReadOnly);
        ComHelper.cutAndResample(dsDem, null, terrainFile, null, wkt, null, null);
        dsDem.delete();
        String landuseFile = inPath + File.separator + config.getLanduseFile();
        Dataset dsLanduse = gdal.Open(landuseTif, gdalconstConstants.GA_ReadOnly);
        ComHelper.cutAndResample(dsLanduse, null, landuseFile, null, wkt, null, null);
        dsLanduse.delete();
        if (stationTif != null) {
            String stationFile = inPath + File.separator + "Station.tif";
            Dataset dsStation = gdal.Open(stationTif, gdalconstConstants.GA_ReadOnly);
            ComHelper.cutAndResample(dsStation, null, stationFile, null, wkt, null, null);
            dsStation.delete();
        }
    }
    public void updateTif(Simu simu, SimuData data2) {
        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<Rainfall> 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<String> 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 + " ";
        //如果没有单位或者单位为mm/h则按照 mm/h计算,否则按照mm/5min计算
//        int unit = StringUtils.isEmpty(data.getIntensityUnit()) || "mm/h".equals(data.getIntensityUnit()) ? 60 : 5;
        String iu = data.getIntensityUnit();
        RainFallUnit rfUnit = RainFallUnit.of(iu);
        data.setIntensityUnit(rfUnit.getUnit());
        int unit = rfUnit.getC();
        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<String> 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<String> 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(Simu simu, SimuData data) throws Exception {
        int duration = 3600 * data.getDuration(); // 秒数
        if (null != data.getRainfalls() && data.getRainfalls().size() > 1) {
            List<Rainfall> rainfalls = data.getRainfalls();
            int size = rainfalls.size();
            Rainfall last = rainfalls.get(size - 1);
            Rainfall first = rainfalls.get(0);
            Instant end = last.getTime().toInstant();
            Instant start = first.getTime().toInstant();
            long diff = ChronoUnit.SECONDS.between(end, start);
            duration = (int) (Math.abs(diff));
        }
        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());
        Double saveFrameInterval = config.getSaveFrameInterval();
        int saveFrames = (int) (duration / saveFrameInterval);
        Short type = simu.getType();
        SimulateType simulateType = SimulateType.of(type);
        String saveMode = simulateType.getSaveMode();
        String startTime = TimeFormatUtil.formatDate(data.getStartTime());
        ConfigVo vo = new ConfigVo(terrainFile, landuseFile, terrainFile, rainfallFile,
                saveName, duration, saveFrames, saveMode, startTime);
        vo.setEvaporation(config.getEvaporation());
        vo.getResult().setSave_filter(config.getSaveFilter());
        String configFile = config.getInPath() + File.separator + data.getInPath() + File.separator + data.getInPath() + ".json";
//        ComHelper.writeJson(configFile, JSON.toJSONString(vo));
        mapper.writeValue(new File(configFile), 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, Collections.emptyList());
//        ComHelper.writeJson(jsonPath, JSON.toJSONString(zarr2Tif));
        mapper.writeValue(new File(jsonPath), 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();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info(line);
                }
            }
            int exitCode = process.waitFor();
            return "ok"; // sb.toString();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            return null;
        }
    }
    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 {
        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<Double> list = getValues(csvPath);
        if (!CollUtil.isEmpty(list)) {
            data.setRainfalls(new ArrayList<>());
            setRainfalls(simu, data, list);
        }
        simu.setData(JSON.toJSONString(data));
    }
    private List<Double> getValues(String csvPath) throws Exception {
        if (!new File(csvPath).exists()) return Collections.emptyList();
        List<String> list = Files.readAllLines(Paths.get(csvPath));
        list.remove(0);
        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<Double> 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<String> 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);
    }
    public List<String> simulationResults(String serviceName) {
        String outPath = config.getOutPath();
        File serviceNameDir = new File(outPath, serviceName);
        List<String> res = new ArrayList<>();
        File[] files = serviceNameDir.listFiles();
        for (File file : files) {
            String name = file.getName();
            if (name.startsWith("layer")) {
                res.add(name);
            }
        }
        return res;
    }
}