dcb
2025-07-01 f31f0991c0d2036e563b886f57de4cf45d3c72cb
实时模拟异步功能实现
已添加2个文件
已修改8个文件
821 ■■■■ 文件已修改
src/main/java/com/se/nsl/config/AsyncExecutor.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/se/nsl/config/PropertiesConfig.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/se/nsl/controller/SimuController.java 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/se/nsl/domain/vo/ConfigVo.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/se/nsl/mapper/SimuMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/se/nsl/service/RealTimeSimulationAsyncService.java 572 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/se/nsl/service/RealTimeSimulationService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/se/nsl/service/ResolveService.java 125 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/SimuMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/se/nsl/config/AsyncExecutor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.se.nsl.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class AsyncExecutor {
    @Bean(name = "realTimeExecutor")
    public Executor realTimeSimulationExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("RealTimeExecutor-");
        executor.initialize();
        return executor;
    }
}
src/main/java/com/se/nsl/config/PropertiesConfig.java
@@ -102,6 +102,8 @@
    private Double saveFilter;
    private Double evaporation;
    public String getVer() {
        return ver;
    }
@@ -473,4 +475,12 @@
    public void setSaveFilter(Double saveFilter) {
        this.saveFilter = saveFilter;
    }
    public Double getEvaporation() {
        return evaporation;
    }
    public void setEvaporation(Double evaporation) {
        this.evaporation = evaporation;
    }
}
src/main/java/com/se/nsl/controller/SimuController.java
@@ -6,9 +6,7 @@
import com.se.nsl.domain.po.Simu;
import com.se.nsl.domain.po.SimuData;
import com.se.nsl.domain.vo.*;
import com.se.nsl.service.RealTimeSimulationService;
import com.se.nsl.service.ResolveService;
import com.se.nsl.service.SimuService;
import com.se.nsl.service.*;
import com.se.nsl.utils.SimulateType;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -34,7 +32,10 @@
    ResolveService resolveService;
    @Resource
    RealTimeSimulationService rts;
    RealTimeSimulationAsyncService rtsas;
    @Resource
    CrossSectionAnalysisService scas;
    /**
     * åˆ†é¡µæŸ¥è¯¢æŽ¨æ¼”模拟
@@ -123,7 +124,7 @@
            if (null == id || id < 1) return fail("id为空");
            Simu simu = simuService.selectById(id);
            if (null == simu) return fail("方案找不到");
            if (null == simu) return notFound("方案找不到");
            if (StringUtils.isEmpty(simu.getData())) return fail("方案数据(JSON)为空");
            SimuData data = JSON.parseObject(simu.getData(), SimuData.class);
@@ -138,8 +139,8 @@
                int rows = resolveService.start(simu);
                return success("ok");
            } else if (simulateType == SimulateType.REAL_TIME) {
                String s = rts.realTimeSimulate(simu);
                return success(s);
                rtsas.startSimulation(simu);
                return success(null, "实时模拟任务已提交");
            }
            return fail("模拟类型暂不支持");
        } catch (Exception ex) {
@@ -201,4 +202,56 @@
            return success(simuResults, simuResults.size());
        }
    }
    @ApiOperation(value = "crossSection")
    @GetMapping("/crossSection")
    public R<Object> crossSection(String serviceName, double[] startPoint, double[] endPoint) {
        if (serviceName == null) {
            return clientError("服务名不能为空");
        }
        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两个值");
        }
        List<CrossSectionAnalysisResult> result = scas.crossSectionAnalysis(serviceName, startPoint, endPoint);
        return success(result);
    }
    @ApiOperation(value = "stop")
    @GetMapping("/stop")
    public R<Object> stop(Integer id) {
        if (id == null) {
            return fail("id不能为空");
        }
        try {
            rtsas.stopSimulation(id);
        } catch (IllegalArgumentException e) {
            return notFound(e.getMessage());
        }
        return success(null, "正在停止id为" + id + "的模拟任务");
    }
    @ApiOperation(value = "results")
    @GetMapping("/results")
    public R<Object> querySimulationResult(Integer id) {
        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 fail("找不到对应的服务");
        }
        List<String> results = resolveService.simulationResults(serviceName);
        return success(results, results.size());
    }
}
src/main/java/com/se/nsl/domain/vo/ConfigVo.java
@@ -29,6 +29,8 @@
    private Boolean variable_dt;
    private double evaporation;
    private ResultVo result;
    public ConfigVo() {
@@ -44,6 +46,7 @@
        this.infiltration = "case1/landuse_to_infiltration.dat";
        this.dt = 0.1;
        this.variable_dt = true;
        this.evaporation = 0;
    }
    public ConfigVo(String terrain, String landuse, String station,
@@ -166,4 +169,12 @@
    public void setResult(ResultVo result) {
        this.result = result;
    }
    public double getEvaporation() {
        return evaporation;
    }
    public void setEvaporation(double evaporation) {
        this.evaporation = evaporation;
    }
}
src/main/java/com/se/nsl/mapper/SimuMapper.java
@@ -18,4 +18,11 @@
    int inserts(@Param("list") List<Simu> list);
    int updates(@Param("list") List<Simu> list);
    /**
     * æ›´æ–°æ¨¡æ‹Ÿç»“果中result字段状态
     * @param simu
     */
    void updateResult(Simu simu);
}
src/main/java/com/se/nsl/service/RealTimeSimulationAsyncService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,572 @@
package com.se.nsl.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.se.nsl.config.PropertiesConfig;
import com.se.nsl.config.RealTimeSimulationConfig;
import com.se.nsl.domain.dto.*;
import com.se.nsl.domain.po.RainGauge;
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.domain.vo.ResultVo;
import com.se.nsl.mapper.SimuMapper;
import com.se.nsl.utils.HttpRequestUtil;
import com.se.nsl.utils.TimeFormatUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Service
@EnableAsync
public class RealTimeSimulationAsyncService {
    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    public static final String JSON_EXT = ".json";
    public static final String DEM_TIF = "DEM.tif";
    public static final String RESULT_ZARR = "result.zarr";
    @Resource
    PropertiesConfig config;
    @Resource
    TestService testService;
    @Resource
    ResolveService resolveService;
    @Resource
    SimuMapper simuMapper;
    private final ObjectMapper mapper = new ObjectMapper();
    @Resource
    private RealTimeSimulationConfig rtsConfig;
    private static final long MILLIS_OF_ONE_DAY = 86400000;
    // å­˜å‚¨ä»»åŠ¡æ‰§è¡ŒçŠ¶æ€
    private final Map<Integer, TaskStatus> taskStatusMap = new ConcurrentHashMap<>();
    // å­˜å‚¨ä»»åŠ¡æ‰§è¡Œçš„Future对象,用于取消任务
    private final Map<String, Future<?>> taskFutureMap = new ConcurrentHashMap<>();
//    @Autowired
//    private ThreadPoolTaskExecutor taskExecutor;
    @Async("realTimeExecutor")
    public void startSimulation(Simu simu) throws IOException {
        int taskId = simu.getId().intValue();
        // æ£€æŸ¥ä»»åŠ¡æ˜¯å¦å·²å­˜åœ¨
        if (taskStatusMap.containsKey(taskId) &&
                taskStatusMap.get(taskId).getStatus() == TaskStatus.Status.RUNNING) {
            throw new IllegalStateException("Task " + taskId + " is already running");
        }
        // åˆå§‹åŒ–任务状态
        TaskStatus status = new TaskStatus();
        status.setTaskId(taskId);
        status.setStatus(TaskStatus.Status.RUNNING);
        taskStatusMap.put(taskId, status);
        updateTaskStatus(simu, "运行中");
        boolean shouldRun = true;
        while(shouldRun) {
            InputStream stream = RealTimeSimulationAsyncService.class.getResourceAsStream("/device_info.json");
            List<DeviceInfo> deviceInfos = mapper.readValue(stream,
                    mapper.getTypeFactory().constructCollectionType(List.class, DeviceInfo.class));
            String serviceName = simu.getServiceName();
            long currentTime = getCurrentTime();
            if (serviceName == null) { //没有进行过模拟计算
                serviceName = TimeFormatUtil.formatTime(currentTime, "yyyyMMddHHmmss");
                simu.setServiceName(serviceName);
                File serviceNameDir = new File(config.getInPath(), serviceName);
                serviceNameDir.mkdir();
                executeSimulateTask(simu, deviceInfos, serviceNameDir, currentTime, serviceName, true);
                simuMapper.updates(Collections.singletonList(simu));
            } else {
                File serviceNameDir = new File(config.getInPath(), serviceName);
                executeSimulateTask(simu, deviceInfos, serviceNameDir, currentTime, serviceName, false);
            }
            try {
                Thread.sleep(TimeUnit.MINUTES.toMillis(5)); //暂停5分钟
            } catch (InterruptedException e) {
                log.error("executing real time simulation exception:", e);
            }
            if (taskStatusMap.get(taskId).getStatus() == TaskStatus.Status.CANCELLED) {
                shouldRun = false;
            }
        }
        updateTaskStatus(simu, "已停止");
        log.info("id为{}的模拟任务已停止", taskId);
    }
    //更新模拟任务的运行状态到数据库
    private void updateTaskStatus(Simu simu, String result) {
        simu.setResult(result);
        simuMapper.updateResult(simu);
    }
    public void stopSimulation(int taskId) {
        TaskStatus status = taskStatusMap.get(taskId);
        if (status == null) {
            throw new IllegalArgumentException("未找到id为" + taskId + "模拟任务");
        }
        if (status.getStatus() == TaskStatus.Status.RUNNING) {
            // æ ‡è®°ä»»åŠ¡ä¸ºå·²å–æ¶ˆ
            status.setCancelled(true);
            status.setStatus(TaskStatus.Status.CANCELLED);
        }
    }
    private long getCurrentTime() {
        double offsetDays = rtsConfig.getOffsetDays();
        long millis = (long) (offsetDays * MILLIS_OF_ONE_DAY);
        return System.currentTimeMillis() - millis;
    }
    private String executeSimulateTask(Simu simu, List<DeviceInfo> deviceInfos, File serviceNameDir,
                                       long currentTime, String serviceName, boolean firstTime) throws IOException {
        SimuData simuData = mapper.readValue(simu.getData(), SimuData.class);
        simuData.setOutPath(serviceName);
        simuData.setInPath(serviceName);
        List<RainGauge> gauges = simuData.getGauges(); //雨量计信息
        if (firstTime) {
            resolveService.initArgs(simu, simuData);
        }
        //根据雨量计读取余量数据
        File newDatFile = generateNewRainfallFile(gauges, deviceInfos, serviceNameDir, currentTime);
        //生成一个新的生成zarr的配置文件
        ConfigVo configVo;
        if (firstTime) {
            configVo = firstTimeZarrConfigFile(serviceNameDir, currentTime, newDatFile);
        } else {
            configVo = generateNewZarrConfigFile(serviceNameDir, serviceName, currentTime, newDatFile);
        }
        File newConfigFile = new File(serviceNameDir, serviceName + JSON_EXT);
        mapper.writeValue(newConfigFile, configVo);
        //执行求解器运算
        String cmd = String.format("%s \"%s\"", config.getUwSolverBat(), newConfigFile);
        callBat(cmd);
        File newZarr2TifJson;
        if (firstTime) {
            newZarr2TifJson = generateNewZarr2TifJson(serviceNameDir, currentTime, Collections.emptyList());
        } else {
            //生成一个新的zarr转tif的json文件
            ResultVo result = configVo.getResult();
            Integer fromFrame = result.getLastFrames();
            Integer toFrame = result.getSave_frames() - 1;
            newZarr2TifJson = generateNewZarr2TifJson(serviceNameDir, currentTime, Arrays.asList(fromFrame, toFrame));
        }
        //执行zarr转tif
        String zarr2TifCmd = String.format("%s \"%s\"", config.getZarr2tifBat(), newZarr2TifJson);
        callBat(zarr2TifCmd);
        //返回新的layer.json名称
        if (firstTime) {
            return generateLayerJsonAndPng(serviceName, serviceNameDir, -1);
        } else {
            return generateLayerJsonAndPng(serviceName, serviceNameDir, currentTime);
        }
    }
    private String generateLayerJsonAndPng(String serviceName, File serviceNameDir,
                                           long currentTime) throws IOException {
        ResultDto resultDto = new ResultDto();
        resultDto.setServiceName(serviceName);
        File temp = Paths.get(config.getOutPath(), serviceName, "temp").toFile();
        resultDto.setTemp(temp.getAbsolutePath());
        resultDto.setOutPath(Paths.get(config.getOutPath(), serviceName).toString());
        File dem = new File(serviceNameDir, DEM_TIF);
        resultDto.setTerrainFile(dem.getAbsolutePath());
        File newDepthDir;
        if (currentTime < 0) {
            newDepthDir = new File(serviceNameDir + File.separator + "depth");
        } else {
            newDepthDir = new File(serviceNameDir + File.separator + "depth_" + currentTime);
        }
        resultDto.setWaterPath(newDepthDir.getAbsolutePath());
        LayerDto layerDto = new LayerDto(config.getVer(), config.getEpsg(), config.getSizes());
        String newLayerJsonName;
        if (currentTime < 0) {
            newLayerJsonName = "layer.json";
        } else {
            newLayerJsonName = "layer_" + currentTime + JSON_EXT;
        }
        layerDto.setName(newLayerJsonName);
        testService.processRealTime(resultDto, layerDto);
        log.info("实时模拟完成");
        return newLayerJsonName;
    }
    private File generateNewZarr2TifJson(File serviceNameDir, long currentTime, List<Object> range) throws IOException {
        Zarr2Tif zarr2Tif;
        File newZarr2TifJson;
        String newZarrPath;
        String geotiffDir;
        if (range.isEmpty()) {
            zarr2Tif = new Zarr2Tif();
            newZarrPath = serviceNameDir + File.separator + RESULT_ZARR;
            geotiffDir = serviceNameDir + File.separator + "depth";
            zarr2Tif.setTerrain_file(serviceNameDir + File.separator + DEM_TIF);
            newZarr2TifJson = new File(serviceNameDir, "zarr2tif.json");
        } else {
            File srcZarr2TifJson = new File(serviceNameDir, "zarr2tif.json");
            zarr2Tif = mapper.readValue(srcZarr2TifJson, Zarr2Tif.class);
            //修改zarr2tif对象中的字段
            newZarrPath = serviceNameDir + File.separator + RESULT_ZARR;
            geotiffDir = serviceNameDir + File.separator + "depth_" + currentTime;
            newZarr2TifJson = new File(serviceNameDir, "zarr2tif_" + currentTime + JSON_EXT);
        }
        zarr2Tif.setGeotiff_dir(geotiffDir);
        zarr2Tif.setZarr_file(newZarrPath);
        zarr2Tif.setRange(range);
        mapper.writeValue(newZarr2TifJson, zarr2Tif);
        return newZarr2TifJson;
    }
    private ConfigVo firstTimeZarrConfigFile(File serviceNameDir, long currentTime, File newDatFile) {
        ConfigVo configVo = new ConfigVo();
        String serviceNameDirPath = serviceNameDir.getAbsolutePath();
        configVo.setTerrain(serviceNameDirPath + File.separator + DEM_TIF);
        configVo.setLanduse(serviceNameDirPath + File.separator + "Landuse.tif");
        File stationFile = new File(serviceNameDir, "Station.tif");
        if (stationFile.exists()) {
            configVo.setStation(stationFile.getAbsolutePath());
        }
        int realTimeSimulateTime = 300; //模拟时间,默认为5min,即300s
        int realTimeInterval = rtsConfig.getRealTimeInterval(); //每帧的间隔时间
        List<String> rainGauge = new ArrayList<>();
        rainGauge.add(newDatFile.getAbsolutePath());//raingage file
        rainGauge.add("mm/min");
        configVo.setRaingage(rainGauge);
        String saveName = serviceNameDir + File.separator + RESULT_ZARR;
        ResultVo result = new ResultVo(saveName, realTimeSimulateTime / realTimeInterval,
                realTimeInterval, "continue", null);
        result.setSave_interval(realTimeInterval);
        result.setSave_timestamp0(TimeFormatUtil.formatTime(currentTime, YYYY_MM_DD_HH_MM_SS));
        configVo.setDuration(realTimeSimulateTime);
        result.setSave_name(saveName);
        result.setSave_filter(config.getSaveFilter());
        configVo.setResult(result);
        return configVo;
    }
    private ConfigVo generateNewZarrConfigFile(File serviceNameDir, String serviceName, long currentTime, File newDatFile) throws IOException {
        File configFile = new File(serviceNameDir, serviceName + JSON_EXT);
        ConfigVo configVo = mapper.readValue(configFile, ConfigVo.class);
        File stationFile = new File(serviceNameDir, "Station.tif");
        if (stationFile.exists()) {
            configVo.setStation(stationFile.getAbsolutePath());
        }
        int realTimeSimulateTime = 300; //模拟时间,默认为5min,即300s
        int realTimeInterval = rtsConfig.getRealTimeInterval(); //每帧的间隔时间
        configVo.getRaingage().set(0, newDatFile.getAbsolutePath()); //raingage file
        ResultVo result = configVo.getResult();
        Integer lastFrames = result.getLastFrames();
        if (lastFrames == null) {
            lastFrames = 0;
        }
        Integer saveStart = result.getSave_start();
        Integer saveInterval = result.getSave_interval();
        Integer saveFrames = result.getSave_frames();
        int newStartPoint = saveStart + (saveFrames - lastFrames) * saveInterval;
        int newSaveFrames = saveFrames + (realTimeSimulateTime / realTimeInterval);
        result.setSave_start(newStartPoint); //起始时间要在上次时间的基础上开始
        result.setLastFrames(saveFrames);
        result.setSave_frames(newSaveFrames); //保留5帧,在原来的基础上增加5帧
        result.setSave_timestamp0(TimeFormatUtil.formatTime(currentTime, YYYY_MM_DD_HH_MM_SS));
        configVo.setDuration(newStartPoint + realTimeSimulateTime);
        return configVo;
    }
    private File generateNewRainfallFile(List<RainGauge> gauges, List<DeviceInfo> deviceInfos,
                                         File serviceNameDir, long currentTime) throws IOException {
        String title = config.getRainfallTitle();
        List<String> newLines = new ArrayList<>();
        newLines.add(title);
        Map<String, Integer> deviceInfoMap = deviceInfos.stream().collect(Collectors.toMap(DeviceInfo::getId, DeviceInfo::getMappingId));
        List<RainRecord> rainRecords = new ArrayList<>();
        for (RainGauge gauge : gauges) {
            String id = gauge.getId();
            Integer mappingId = deviceInfoMap.getOrDefault(id, 0);
            RainRecord rr = getRainGaugeResult(gauge, mappingId, currentTime);
            rainRecords.add(rr);
        }
        fillEmptyValue(rainRecords); //填充没有值的雨量计
        for (RainRecord rr : rainRecords) {
            newLines.add(rr.toString());
        }
        File newDatFile = new File(serviceNameDir, "rainfall_" + currentTime + ".dat");
        if (!newDatFile.exists()) newDatFile.createNewFile();
        Files.write(newDatFile.toPath(), newLines, StandardOpenOption.TRUNCATE_EXISTING);
        return newDatFile;
    }
    //将雨量计中空值(intensity为-1)的部分填充值
    private void fillEmptyValue(List<RainRecord> rainRecords) {
        double[] rainValues = rainRecords.stream().mapToDouble(r -> r.getIntensity()).toArray();
        double[] forwardValues = forwardFill(rainValues);
        double[] backwordValues = backwordFill(rainValues);
        for (int i = 0; i < rainRecords.size(); i++) {
            RainRecord rr = rainRecords.get(i);
            double filledValue = forwardValues[i] != -1 ? forwardValues[i] : backwordValues[i];
            rr.setIntensity(filledValue);
        }
        //如果都是-1,则设置为0
        for (RainRecord rr : rainRecords) {
            double intensity = rr.getIntensity();
            if (intensity == -1) {
                rr.setIntensity(0);
            }
        }
    }
    private double[] forwardFill(double[] rainValues) {
        double[] result = Arrays.copyOf(rainValues, rainValues.length);
        double lastValid = -1D;
        for (int i = 0; i < result.length; i++) {
            if (result[i] != -1) {
                lastValid = result[i];
            } else if (lastValid != -1) {
                result[i] = lastValid;
            }
        }
        return result;
    }
    private double[] backwordFill(double[] rainValues) {
        double[] result = Arrays.copyOf(rainValues, rainValues.length);
        double lastValid = -1D;
        for (int i = result.length - 1; i >= 0; i--) {
            if (result[i] != -1) {
                lastValid = result[i];
            } else if (lastValid != -1) {
                result[i] = lastValid;
            }
        }
        return result;
    }
    private RainRecord getRainGaugeResult(RainGauge gauge, int stationId, long currentTime) throws JsonProcessingException {
        double lon = gauge.getX();
        double lat = gauge.getY();
        LocalDateTime dateTime = TimeFormatUtil.toDate(currentTime);
        int year = dateTime.getYear();
        int month = dateTime.getMonthValue();
        int day = dateTime.getDayOfMonth();
        int hour = dateTime.getHour();
        int minute = dateTime.getMinute();
        String id = gauge.getId();
        long someMinutesAgo = currentTime - TimeUnit.MINUTES.toMillis(rtsConfig.getRequestOffsetMinutes());
        String startTime = TimeFormatUtil.formatTime(someMinutesAgo, YYYY_MM_DD_HH_MM_SS);
        String endTime = TimeFormatUtil.formatTime(currentTime, YYYY_MM_DD_HH_MM_SS);
        double intensity = getIntensityByDeviceId(id, startTime, endTime); //保留指定位数小数
//        return String.format("%s %s %s %s %s %s %s %s %s",
//                stationId, lon, lat, year, month, day, hour, minute, String.format("%.6f", intensity));
        return new RainRecord(stationId, lon, lat, year, month, day, hour, minute, intensity);
    }
    //根据雨量计标识查询雨量数据
    private double getIntensityByDeviceId(String deviceId, String startTime, String endTime) throws JsonProcessingException {
        RemoteGaugeInput input = new RemoteGaugeInput();
        input.setCurrentPage(1);
        input.setPageSize(2);
        RemoteGaugeInput.FilterObject filterObject = new RemoteGaugeInput.FilterObject();
        filterObject.setDeviceCode(deviceId);
        filterObject.setSendTimeList(Arrays.asList(startTime, endTime));
        input.setFilterObject(filterObject);
        String url = rtsConfig.getUrl();
        String token = rtsConfig.getToken();
        ResponseEntity<String> post = HttpRequestUtil.post(url, input, String.class, token);
        String body = post.getBody();
        JsonNode jsonNode = mapper.readTree(body);
        JsonNode pageData = jsonNode.get("data").get("pageData");
        if (!pageData.isEmpty()) {
            return pageData.get(0).get("value").asDouble();
        }
        return -1D;
    }
    private String callBat(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 "" + exitCode; // sb.toString();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            return null;
        }
    }
    static class RainRecord {
        private int stationId;
        private double lon;
        private double lat;
        private int year;
        private int month;
        private int day;
        private int hour;
        private int minute;
        private double intensity; //-1代表没有数值
        public RainRecord() {
        }
        public RainRecord(int stationId, double lon, double lat, int year, int month,
                          int day, int hour, int minute, double intensity) {
            this.stationId = stationId;
            this.lon = lon;
            this.lat = lat;
            this.year = year;
            this.month = month;
            this.day = day;
            this.hour = hour;
            this.minute = minute;
            this.intensity = intensity;
        }
        public int getStationId() {
            return stationId;
        }
        public void setStationId(int stationId) {
            this.stationId = stationId;
        }
        public double getLon() {
            return lon;
        }
        public void setLon(double lon) {
            this.lon = lon;
        }
        public double getLat() {
            return lat;
        }
        public void setLat(double lat) {
            this.lat = lat;
        }
        public int getYear() {
            return year;
        }
        public void setYear(int year) {
            this.year = year;
        }
        public int getMonth() {
            return month;
        }
        public void setMonth(int month) {
            this.month = month;
        }
        public int getDay() {
            return day;
        }
        public void setDay(int day) {
            this.day = day;
        }
        public int getHour() {
            return hour;
        }
        public void setHour(int hour) {
            this.hour = hour;
        }
        public int getMinute() {
            return minute;
        }
        public void setMinute(int minute) {
            this.minute = minute;
        }
        public double getIntensity() {
            return intensity;
        }
        public void setIntensity(double intensity) {
            this.intensity = intensity;
        }
        @Override
        public String toString() {
            return "" + stationId + " " + lon + " " + lat +
                    " " + year + " " + month + " " + day +
                    " " + hour + " " + minute + " " + String.format("%.6f", intensity);
        }
    }
    // ä»»åŠ¡çŠ¶æ€å†…éƒ¨ç±»
    static class TaskStatus {
        public enum Status {
            RUNNING, COMPLETED, FAILED, CANCELLED
        }
        private int taskId;
        private Status status;
        private boolean cancelled;
        private String errorMessage;
        public int getTaskId() {
            return taskId;
        }
        public void setTaskId(int taskId) {
            this.taskId = taskId;
        }
        public Status getStatus() {
            return status;
        }
        public void setStatus(Status status) {
            this.status = status;
        }
        public boolean isCancelled() {
            return cancelled;
        }
        public void setCancelled(boolean cancelled) {
            this.cancelled = cancelled;
        }
        public String getErrorMessage() {
            return errorMessage;
        }
        public void setErrorMessage(String errorMessage) {
            this.errorMessage = errorMessage;
        }
    }
}
src/main/java/com/se/nsl/service/RealTimeSimulationService.java
@@ -50,7 +50,7 @@
    private RealTimeSimulationConfig rtsConfig;
    private static final long MILLIS_OF_ONE_DAY = 86400000;
    public String realTimeSimulate(Simu simu) throws IOException {
    public String startSimulation(Simu simu) throws IOException {
        InputStream stream = RealTimeSimulationService.class.getResourceAsStream("/device_info.json");
        List<DeviceInfo> deviceInfos = mapper.readValue(stream,
                mapper.getTypeFactory().constructCollectionType(List.class, DeviceInfo.class));
@@ -181,6 +181,7 @@
        String serviceNameDirPath = serviceNameDir.getAbsolutePath();
        configVo.setTerrain(serviceNameDirPath + File.separator + DEM_TIF);
        configVo.setLanduse(serviceNameDirPath + File.separator + "Landuse.tif");
        configVo.setEvaporation(config.getEvaporation());
        File stationFile = new File(serviceNameDir, "Station.tif");
        if (stationFile.exists()) {
            configVo.setStation(stationFile.getAbsolutePath());
src/main/java/com/se/nsl/service/ResolveService.java
@@ -342,6 +342,7 @@
        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));
@@ -517,115 +518,17 @@
        Files.write(Paths.get(dat), list, StandardCharsets.UTF_8);
    }
    //实时模拟
//    public String realTimeSimulate(RealTimeInput input) throws IOException {
//        long currentTime = System.currentTimeMillis();
//        //根据服务找到指定的文件夹
//        String serviceName = input.getServiceName();
//        File serviceNameDir = new File(config.getInPath(), serviceName);
//        //生成一个新的雨量文件,需要原先雨量文件的一些信息,所以需要先读取旧的
//        String[] values = readTheOldFirstLineRainfallValue(serviceNameDir);
//        File newDatFile = generateNewRainfallFile(input, values, serviceNameDir, currentTime);
//
//        //生成一个新的生成zarr的配置文件
//        File newConfigFile = generateNewZarrConfigFile(serviceNameDir, serviceName, currentTime, newDatFile);
//        //执行求解器运算
//        String cmd = String.format("%s \"%s\"", config.getUwSolverBat(), newConfigFile);
//        callBat2(cmd);
//
//        //生成一个新的zarr转tif的json文件
//        File newZarr2TifJson = generateNewZarr2TifJson(serviceNameDir, currentTime);
//        //执行zarr转tif
//        String zarr2TifCmd = String.format("%s \"%s\"", config.getZarr2tifBat(), newZarr2TifJson);
//        callBat2(zarr2TifCmd);
//        //返回新的layer.json名称
//        return generateLayerJsonAndPng(serviceName, serviceNameDir, currentTime);
//
//    }
//    private String generateLayerJsonAndPng(String serviceName, File serviceNameDir, long currentTime) throws IOException {
//        ResultDto resultDto = new ResultDto();
//        resultDto.setServiceName(serviceName);
//        File temp = Paths.get(config.getOutPath(), serviceName, "temp").toFile();
//        if (!temp.exists()) temp.mkdir();
//        resultDto.setTemp(temp.getAbsolutePath());
//        resultDto.setOutPath(Paths.get(config.getOutPath(), serviceName).toString());
//        File dem = new File(serviceNameDir, "DEM.tif");
//        resultDto.setTerrainFile(dem.getAbsolutePath());
//        File newDepthDir = new File(serviceNameDir + File.separator + "depth_" + currentTime);
//        resultDto.setWaterPath(newDepthDir.getAbsolutePath());
//        LayerDto layerDto = new LayerDto(config.getVer(), 4548, config.getSizes());
//        String newLayerJsonName = "layer_" + currentTime + ".json";
//        layerDto.setName(newLayerJsonName);
//        testService.processRealTime(resultDto, layerDto);
//        return newLayerJsonName;
//    }
//    private File generateNewZarr2TifJson(File serviceNameDir, long currentTime) throws IOException {
//        File srcZarr2TifJson = new File(serviceNameDir, "zarr2tif.json");
//        Zarr2Tif zarr2Tif = mapper.readValue(srcZarr2TifJson, Zarr2Tif.class);
//        //修改zarr2tif对象中的字段
////        String stamp = TimeFormatUtil.formatTime(currentTime, "yyyy-MM-dd HH:mm:ss");
////        zarr2Tif.setStart_timestamp(stamp);
////        String newZarrPath = serviceNameDir + File.separator + "result_" + currentTime + ".zarr";
//        String newZarrPath = serviceNameDir + File.separator + "result.zarr";
//        zarr2Tif.setZarr_file(newZarrPath);
//        zarr2Tif.setGeotiff_dir(serviceNameDir + File.separator + "depth_" + currentTime);
//        File newZarr2TifJson = new File(serviceNameDir, "zarr2tif_" + currentTime + ".json");
//        mapper.writeValue(newZarr2TifJson, zarr2Tif);
//        return newZarr2TifJson;
//    }
//    private File generateNewZarrConfigFile(File serviceNameDir, String serviceName, long currentTime, File newDatFile) throws IOException {
//        File configFile = new File(serviceNameDir, serviceName + ".json");
//        ConfigVo configVo = mapper.readValue(configFile, ConfigVo.class);
//        int simulateTime = 300; //模拟时间,默认为5min,即300s
//        int intervalTime = 60; //每帧的间隔时间,默认为60s,60s生成一帧
//        configVo.getRaingage().set(0, newDatFile.getAbsolutePath()); //raingage file
//        ResultVo result = configVo.getResult();
//        Integer oldDuration = configVo.getDuration();
////        result.setSave_start(oldDuration); //起始时间要在上次时间的基础上开始
//        configVo.setDuration(oldDuration + simulateTime); //固定为5min
//        result.setSave_interval(intervalTime);
//        result.setSave_frames(result.getSave_frames() + (simulateTime / intervalTime)); //保留5帧,在原来的基础上增加5帧
////        String newZarrPath = serviceNameDir + File.separator + "result_" + currentTime + ".zarr";
//        String newZarrPath = serviceNameDir + File.separator + "result.zarr";
//        result.setSave_name(newZarrPath);
//        File newConfigFile = new File(serviceNameDir, currentTime + ".json");
//        mapper.writeValue(newConfigFile, configVo);
//        return newConfigFile;
//    }
//    private File generateNewRainfallFile(RealTimeInput input, String[] values, File serviceNameDir, long currentTime) throws IOException {
//        String station = values[0];
//        double lon = Double.parseDouble(values[1]);
//        double lat = Double.parseDouble(values[2]);
//        String title = config.getRainfallTitle();
//        List<String> newLines = new ArrayList<>();
//        newLines.add(title);
//        List<RealTimeInput.RealTimeData> data = input.getData();
//        for (RealTimeInput.RealTimeData rd : data) {
//            LocalDateTime dateTime = rd.getDateTime();
//            int year = dateTime.getYear();
//            int month = dateTime.getMonthValue();
//            int day = dateTime.getDayOfMonth();
//            int hour = dateTime.getHour();
//            int minute = dateTime.getMinute();
//            double intensity = rd.getIntensity(); //保留指定位数小数
//            String l = String.format("%s %s %s %s %s %s %s %s %s",
//                    station, lon, lat, year, month, day, hour, minute, String.format("%.6f", intensity));
//            newLines.add(l);
//        }
//        File newDatFile = new File(serviceNameDir, "rainfall_" + currentTime + ".dat");
//        if (!newDatFile.exists()) newDatFile.createNewFile();
//        Files.write(newDatFile.toPath(), newLines, StandardOpenOption.TRUNCATE_EXISTING);
//        return newDatFile;
//    }
//    private static String[] readTheOldFirstLineRainfallValue(File serviceNameDir) throws IOException {
//        File srcRailfallFile = new File(serviceNameDir, "rainfall.dat");
//        List<String> lines = Files.readAllLines(srcRailfallFile.toPath());
//        String secondLine = lines.get(1);
//        return secondLine.split(" ");
//    }
    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;
    }
}
src/main/resources/application-dev.yml
@@ -152,6 +152,7 @@
  #生成帧数的间隔时间,单位是分钟,设置为5表示每隔5分钟生成一帧
  saveFrameInterval: 20
  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
  landuse: 2
  #sizes: 64,128,256,512,1024,2048,4096
src/main/resources/mapper/SimuMapper.xml
@@ -83,4 +83,8 @@
            where id = #{item.id}
        </foreach>
    </update>
    <update id="updateResult" parameterType="com.se.nsl.domain.po.Simu">
        update nsl.tbl_yj_tr_simulate set result=#{result} where id = #{id}
    </update>
</mapper>