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.RealTimeSimulationConfig; import com.se.nsl.config.PropertiesConfig; 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.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.TimeUnit; import java.util.stream.Collectors; @Slf4j @Service public class RealTimeSimulationService { 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; public String startSimulation(Simu simu) throws IOException { InputStream stream = RealTimeSimulationService.class.getResourceAsStream("/device_info.json"); List 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(); String layerJsonName = executeSimulateTask(simu, deviceInfos, serviceNameDir, currentTime, serviceName, true); simuMapper.updates(Collections.singletonList(simu)); return layerJsonName; } else { File serviceNameDir = new File(config.getInPath(), serviceName); return executeSimulateTask(simu, deviceInfos, serviceNameDir, currentTime, serviceName ,false); } } 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 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 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 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"); configVo.setEvaporation(config.getEvaporation()); File stationFile = new File(serviceNameDir, "Station.tif"); if (stationFile.exists()) { configVo.setStation(stationFile.getAbsolutePath()); } int realTimeSimulateTime = 300; //模拟时间,默认为5min,即300s Double realTimeInterval = rtsConfig.getFrameInterval(); //每帧的间隔时间 List 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, (int) (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 Double realTimeInterval = rtsConfig.getFrameInterval(); //每帧的间隔时间 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(); Double saveInterval = result.getSave_interval(); Integer saveFrames = result.getSave_frames(); int newStartPoint = (int) (saveStart + (saveFrames - lastFrames) * saveInterval); int newSaveFrames = (int) (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 gauges, List deviceInfos, File serviceNameDir, long currentTime) throws IOException { String title = config.getRainfallTitle(); List newLines = new ArrayList<>(); newLines.add(title); Map deviceInfoMap = deviceInfos.stream().collect(Collectors.toMap(DeviceInfo::getId, DeviceInfo::getMappingId)); List 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 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 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); } } }