代码提交点位地图

This commit is contained in:
lilin 2025-07-31 14:15:16 +08:00
parent 17bf8a98df
commit d145df182a
5 changed files with 693 additions and 165 deletions

View File

@ -686,17 +686,18 @@ public class TsFilesController {
* 参数说明 id 文件的ID
* 参数说明 samTimes 时间
* 参数说明 token SSE连接的token
* 参数说明 config 配置文件的ID
* 返回值说明: com.yfd.platform.config.ResponseResult
***********************************/
@Log(module = "实验数据管理", value = "实时获取轨迹数据!")
@PostMapping("/startSimpleNavi")
@ApiOperation("实时获取轨迹数据")
public ResponseResult startSimpleNavi(String id, int samTimes, String token, String taskId) {
public ResponseResult startSimpleNavi(String id, int samTimes, String token, String taskId,String configId) {
try {
// 使用线程池异步执行任务
CompletableFuture.runAsync(() -> {
try {
tsFilesService.batchSendNaviOutDataJob(id, samTimes, token, taskId);
tsFilesService.batchSendNaviOutDataJob(id, samTimes, token, taskId,configId);
} catch (Exception e) {
e.printStackTrace();
}
@ -794,4 +795,24 @@ public class TsFilesController {
FileItemResult fileItemResult = tsFilesService.obtainUrl(id, type, taskId);
return ResponseResult.successData(fileItemResult);
}
/**********************************
* 用途说明: 通过文件ID获取对应的txt集合和png集合
* 参数说明 id 文件夹ID
* 参数说明 taskId 任务ID
* 参数说明 nodeId 节点ID
* 返回值说明: com.yfd.platform.config.ResponseResult 返回文件集合列表
***********************************/
@Log(module = "实验数据管理", value = "通过文件ID获取对应的txt集合和png集合")
@PostMapping("/listTsFilesById")
@ApiOperation("通过文件ID获取对应的txt集合和png集合")
public ResponseResult listTsFilesById(String id, String taskId,String nodeId) {
if (StrUtil.isBlank(id) && StrUtil.isBlank(taskId)&& StrUtil.isBlank(nodeId)) {
return ResponseResult.error("参数为空");
}
//通过ID查询集合
return ResponseResult.successData(tsFilesService.listTsFilesById(id, taskId,nodeId));
}
}

View File

@ -0,0 +1,30 @@
package com.yfd.platform.modules.experimentalData.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.util.List;
@Data
public class TrajectoriesDto {
/**
* 获取底图集合
*/
@TableField(exist = false)
private List<TsFiles> tsFilesListPng;
/**
* 获取地理信息集合
*/
@TableField(exist = false)
private List<TsFiles> tsFilesListTxt;
/**
* 获取配置文件信息
*/
@TableField(exist = false)
private List<TsFiles> tsFilesListConfig;
}

View File

@ -191,4 +191,13 @@ public class TsFiles implements Serializable {
@TableField(exist = false)
private String minioMd5;
/**
* 文件内容
*/
@TableField(exist = false)
private String fileContent;
}

View File

@ -144,7 +144,7 @@ public interface ITsFilesService extends IService<TsFiles> {
* 参数说明 token SSE连接的token
* 返回值说明: com.yfd.platform.config.ResponseResult
***********************************/
void batchSendNaviOutDataJob(String id, int samTimes, String token,String taskId);
void batchSendNaviOutDataJob(String id, int samTimes, String token,String taskId,String configId);
/**
* 查询文件内容接口
@ -256,4 +256,12 @@ public interface ITsFilesService extends IService<TsFiles> {
void decompressionFolderAsync(String id, String decompressionPath, String parentId, String path, String taskId) throws IOException;
Object decompressionFolderData(String taskId);
/**********************************
* 用途说明: 通过文件ID获取对应的txt集合和png集合
* 参数说明 id 文件夹ID
* 参数说明 taskId 任务ID
* 参数说明 nodeId 节点ID
* 返回值说明: com.yfd.platform.config.ResponseResult 返回文件集合列表
***********************************/
Object listTsFilesById(String id, String taskId, String nodeId);
}

View File

@ -89,6 +89,8 @@ import java.util.zip.*;
import org.apache.commons.codec.binary.Hex;
import static java.lang.Float.parseFloat;
/**
* <p>
@ -333,7 +335,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
}
private boolean isValidPage(IPage<TsFiles> page) {
return page.getRecords() != null
&& !page.getRecords().isEmpty()
@ -3327,6 +3328,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
// })
// .collect(Collectors.toList());
//
/// / List<TsFiles> filteredRecords = tsFilesPage.getRecords().stream()
/// / .filter(tsFile -> {
/// / try {
@ -3377,8 +3379,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
// TableNameContextHolder.clear();
// }
// }
@Override
public List<TsFiles> compareMd5List(List<String> dataset, String nodeId, String taskId) {
@ -3742,7 +3742,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
}
/**********************************
* 用途说明: 将文件上传到备份空间 其他接口使用
* 参数说明 parameter 数据集合
@ -5407,8 +5406,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
}
/*******************************************读取文件内容经纬度**************************************************************/
/**********************************
* 用途说明: 实时获取轨迹数据
@ -5418,12 +5415,17 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
* 返回值说明: com.yfd.platform.config.ResponseResult
***********************************/
@Override
public void batchSendNaviOutDataJob(String id, int samTimes, String token, String taskId) {
public void batchSendNaviOutDataJob(String id, int samTimes, String token, String taskId,String configId) {
try {
TsTask tsTask = tsTaskMapper.selectById(taskId);
TableNameContextHolder.setTaskCode(tsTask.getTaskCode());
TsFiles tsFiles = tsFilesMapper.selectById(id);
//判断文件后缀是.txt还是.csv
String fileName = tsFiles.getFileName();
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
if ("txt".equals(suffix)) {
if (currentTaskFuture != null && !currentTaskFuture.isDone()) {
currentTaskFuture.cancel(true); // 中断之前的任务
}
@ -5436,6 +5438,40 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toFile()))) {
reader.readLine(); // 跳过标题行
SimpleNaviData data1 = null;
int result = 0;
// 读取第二行数据
String secondLine = reader.readLine();
if (secondLine != null) {
secondLine = secondLine.trim();
String[] valuesSecond = secondLine.split("\t");
data1 = parseLine(valuesSecond);
}
// 读取第三行数据
String thirdLine = reader.readLine();
if (thirdLine != null) {
thirdLine = thirdLine.trim();
String[] valuesThird = thirdLine.split("\t");
SimpleNaviData data2 = parseLine(valuesThird);
// 假设 UtcTime String 类型首先转换为 double 类型
double time1 = Double.parseDouble(data1.getUtcTime()); // 如果 getUtcTime() 返回 String 类型
double time2 = Double.parseDouble(data2.getUtcTime()); // 如果 getUtcTime() 返回 String 类型
// 计算差值
double diff = time2 - time1;
// 确保差值不为零以避免除以零错误
if (diff != 0) {
result = (int) Math.floor(1 / diff);
} else {
result = 1;
}
}
// 不管 samTimes 如何先读取第一行数据
String firstLine = reader.readLine();
if (firstLine != null) {
@ -5449,9 +5485,11 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
}
}
//todo 这个地方该从一下 换成200
int step = samTimes * 200; // 计算行号间隔
int step = samTimes * result; // 计算行号间隔
int lineCount = 1; // 从第一行开始
while (!Thread.currentThread().isInterrupted()) {
lineCount += step; // 直接跳到下一个需要读取的行
String line = null;
@ -5499,6 +5537,46 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
e.printStackTrace();
}
});
}else{
TsFiles tsFilesData = tsFilesMapper.selectById(configId);
if (currentTaskFuture != null && !currentTaskFuture.isDone()) {
currentTaskFuture.cancel(true);
}
currentTaskFuture = executorService.submit(() -> {
try {
// 1. 获取配置文件路径
StorageSourceConfig config = getStorageSourceConfig("filePath", "local", tsTask.getLocalStorageId());
Path basePath = Paths.get(config.getValue() + tsFilesData.getWorkPath());
// 2. 读取配置文件假设配置文件名为 trj_config.txt
Path configPath = basePath.resolve("trj_config.txt");
Map<String, String> columnMapping = parseConfigFile(configPath);
String timeColumn = columnMapping.get("time");
String lonColumn = columnMapping.get("lon");
String latColumn = columnMapping.get("lat");
String hgtColumn = columnMapping.get("hgt");
LOGGER.info("列映射: time={}, lon={}, lat={}, hgt={}",
timeColumn, lonColumn, latColumn, hgtColumn);
// 3. 获取数据文件路径
Path dataPath = basePath.resolve("VINS.csv");
// 4. 处理数据文件
processDataFile(dataPath, samTimes, token, timeColumn, lonColumn, latColumn, hgtColumn);
} catch (Exception e) {
LOGGER.error("任务执行失败: {}", e.getMessage(), e);
}
});
}
} catch (Exception e) {
LOGGER.error("未知异常:{}", e.getMessage(), e);
throw new RuntimeException("系统错误,请联系管理员");
@ -5544,6 +5622,232 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
}
/**
* 读取配置文件并解析为列名映射
*
* @param configFilePath 配置文件路径
* @return 列名映射Maptime, lon, lat, hgt
* @throws IOException 文件读取错误
*/
public Map<String, String> parseConfigFile(Path configFilePath) throws IOException {
Map<String, String> columnMapping = new HashMap<>();
try (BufferedReader reader = Files.newBufferedReader(configFilePath, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
// 分割键值对
String[] parts = line.split(":");
if (parts.length < 2) {
LOGGER.warn("忽略无效行: {}", line);
continue;
}
String key = parts[0].trim().toLowerCase();
String value = parts[1].trim();
// 只处理需要的键
if (key.equals("time") || key.equals("lon") ||
key.equals("lat") || key.equals("hgt")) {
columnMapping.put(key, value);
}
}
}
// 验证是否获取到所有必需的列
if (columnMapping.size() < 4) {
throw new IOException("配置文件缺少必需的列定义");
}
return columnMapping;
}
/**
* 处理数据文件
*/
private void processDataFile(Path dataPath, int samTimes, String token,
String timeColumn, String lonColumn,
String latColumn, String hgtColumn)
throws IOException {
try (BufferedReader reader = Files.newBufferedReader(dataPath, StandardCharsets.UTF_8)) {
// 1. 读取标题行
String headerLine = reader.readLine();
if (headerLine == null) {
LOGGER.error("数据文件为空: {}", dataPath);
return;
}
// 2. 解析标题行 - 使用逗号分隔
String[] headers = headerLine.split(","); // 改为逗号分隔
// 添加标题行调试日志
LOGGER.info("数据文件标题行包含 {} 列", headers.length);
LOGGER.debug("完整标题行: {}", String.join("|", headers));
Map<String, Integer> columnIndices = new HashMap<>();
// 3. 查找列索引修复后的逻辑
for (int i = 0; i < headers.length; i++) {
String header = headers[i].trim();
// 使用 equalsIgnoreCase 进行不区分大小写的匹配
if (header.equalsIgnoreCase(timeColumn)) {
columnIndices.put("time", i);
LOGGER.info("找到 time 列 '{}' 在位置 {}", header, i);
}
if (header.equalsIgnoreCase(lonColumn)) {
columnIndices.put("lon", i);
LOGGER.info("找到 lon 列 '{}' 在位置 {}", header, i);
}
if (header.equalsIgnoreCase(latColumn)) {
columnIndices.put("lat", i);
LOGGER.info("找到 lat 列 '{}' 在位置 {}", header, i);
}
if (header.equalsIgnoreCase(hgtColumn)) {
columnIndices.put("hgt", i);
LOGGER.info("找到 hgt 列 '{}' 在位置 {}", header, i);
}
}
// 4. 验证列是否存在
List<String> missingColumns = new ArrayList<>();
if (!columnIndices.containsKey("time")) missingColumns.add(timeColumn);
if (!columnIndices.containsKey("lon")) missingColumns.add(lonColumn);
if (!columnIndices.containsKey("lat")) missingColumns.add(latColumn);
if (!columnIndices.containsKey("hgt")) missingColumns.add(hgtColumn);
if (!missingColumns.isEmpty()) {
LOGGER.error("以下列在数据文件中不存在: {}", String.join(", ", missingColumns));
LOGGER.error("数据文件标题行: {}", headerLine);
return;
}
int timeIndex = columnIndices.get("time");
int lonIndex = columnIndices.get("lon");
int latIndex = columnIndices.get("lat");
int hgtIndex = columnIndices.get("hgt");
LOGGER.info("列索引: time={}, lon={}, lat={}, hgt={}",
timeIndex, lonIndex, latIndex, hgtIndex);
// 5. 读取并处理数据行
String line;
int lineCount = 0;
int lineCountData = 0;
float timeValue0 = 0;
float timeValue1 = 0;
int result = 0; //
while (!Thread.currentThread().isInterrupted() && (line = reader.readLine()) != null) {
lineCountData++;
if (lineCountData > 2) {
break; // 跳出循环停止读取数据
}
// 使用逗号分隔数据行
String[] values = line.split(","); // 改为逗号分隔
if (lineCountData == 1) {
// 读取第一行的时间值
timeValue0 = Float.parseFloat(getValueSafely(values, timeIndex, "0.0")); // 将字符串转换为数字
} else if (lineCountData == 2) {
// 读取第二行的时间值
timeValue1 = Float.parseFloat(getValueSafely(values, timeIndex, "0.0")); // 将字符串转换为数字
// 计算 timeValue1 - timeValue0
float data = timeValue1 - timeValue0;
// 计算 1 / data
if (data != 0) { // 确保避免除以零
result = (int) Math.floor(1 / data);
} else {
result = 1;
}
}
}
while (!Thread.currentThread().isInterrupted() && (line = reader.readLine()) != null) {
lineCount++;
// 使用逗号分隔数据行
String[] values = line.split(","); // 改为逗号分隔
if (lineCount > 1 && (lineCount - 1) % (samTimes * result) != 0) {
continue;
}
// 检查数据完整性
int maxIndex = getMaxIndex(timeIndex, lonIndex, latIndex, hgtIndex);
if (values.length <= maxIndex) {
LOGGER.warn("行 {} 数据不完整: {}", lineCount, line);
continue;
}
// 发送数据
sendDataCsv(token, values, lineCount, timeIndex, lonIndex, latIndex, hgtIndex);
// 每次发送后都休眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
if (!Thread.currentThread().isInterrupted()) {
LOGGER.info("文件处理完成,共处理 {} 行", lineCount);
}
}
}
// 辅助方法获取最大索引
private int getMaxIndex(int... indices) {
int max = -1;
for (int index : indices) {
if (index > max) max = index;
}
return max;
}
private void sendDataCsv(String token, String[] values, int lineCount,
int timeIndex, int lonIndex, int latIndex, int hgtIndex) {
if (Thread.currentThread().isInterrupted()) {
return;
}
try {
SimpleNaviData data = new SimpleNaviData();
data.setUtcTime(getValueSafely(values, timeIndex, "0.0"));
data.setLon(getValueSafely(values, lonIndex, "0.0"));
data.setLat(getValueSafely(values, latIndex, "0.0"));
data.setAlt(getValueSafely(values, hgtIndex, "0.0"));
String jsonData = JSONUtil.toJsonStr(data);
ServerSendEventServer.sendMessageById(token, jsonData);
LOGGER.info("Line {} sent at: {}", lineCount, System.currentTimeMillis());
} catch (Exception e) {
LOGGER.error("发送数据失败: {}", e.getMessage());
if (e.getCause() instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
}
}
/**
* 查询文件内容接口
*
@ -5871,6 +6175,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
}
ServerSendEventServer.removeUser(token);
}
// 辅助方法获取存储配置
private StorageSource getStorageConfig(Integer id) {
return storageSourceMapper.selectOne(new LambdaQueryWrapper<StorageSource>().eq(StorageSource::getId, id)
@ -5884,6 +6189,161 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
);
}
/**********************************
* 用途说明: 通过文件ID获取对应的txt集合和png集合
* 参数说明 id 文件夹ID
* 参数说明 taskId 任务ID
* 参数说明 nodeId 节点ID
* 返回值说明: com.yfd.platform.config.ResponseResult 返回文件集合列表
***********************************/
@Override
public Object listTsFilesById(String id, String taskId, String nodeId) {
try {
TrajectoriesDto trajectoriesDto = new TrajectoriesDto();
//图片集合
List<TsFiles> tsFilesListPng = new ArrayList<>();
//txt集合
List<TsFiles> tsFilesListTxt = new ArrayList<>();
//config集合
List<TsFiles> tsFilesListConfig = new ArrayList<>();
//1:动态表名 以及通过ID查询tsfiles 然后根据id path taskId nodeid 等等条件查询所欲的集合
TsTask tsTask = tsTaskMapper.selectById(taskId);
TableNameContextHolder.setTaskCode(tsTask.getTaskCode());
TsFiles tsFile = tsFilesMapper.selectById(id);
if (tsFile == null) {
return new ArrayList<>();
}
LambdaQueryWrapper<TsFiles> queryWrapperPng = new LambdaQueryWrapper<>();
queryWrapperPng.eq(TsFiles::getTaskId, taskId)
.eq(TsFiles::getNodeId, nodeId)
.eq(TsFiles::getWorkPath, tsFile.getWorkPath())
.eq(TsFiles::getParentId, tsFile.getParentId())
.eq(TsFiles::getIsFile, "FILE")
// 图片文件类型匹配多种图片格式
.and(wrapper -> wrapper
.likeLeft(TsFiles::getFileName, ".png")
.or().likeLeft(TsFiles::getFileName, ".jpg")
.or().likeLeft(TsFiles::getFileName, ".jpeg")
);
List<TsFiles> listTsFilesPng = tsFilesMapper.selectList(queryWrapperPng);
if (listTsFilesPng == null) {
trajectoriesDto.setTsFilesListPng(new ArrayList<>());
}else {
for (TsFiles tsFiles1 : listTsFilesPng) {
FileItemResult fileItemResult = new FileItemResult();
//获取图片url
String fileNameData = tsFiles1.getFileName();
String path = "";
StorageSource storageSourceLocal = null;
storageSourceLocal = getStorageConfig(tsTask.getLocalStorageId());
String workPath = tsFiles1.getWorkPath();
path = workPath + fileNameData;
//准备获取文件的信息
AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(storageSourceLocal.getKey());
fileItemResult = fileService.getFileItem(path);
tsFiles1.setUrl(fileItemResult.getUrl());
tsFilesListPng.add(tsFiles1);
}
trajectoriesDto.setTsFilesListPng(listTsFilesPng);
}
LambdaQueryWrapper<TsFiles> queryWrapperTxt = new LambdaQueryWrapper<>();
queryWrapperTxt.eq(TsFiles::getTaskId, taskId)
.eq(TsFiles::getNodeId, nodeId)
.eq(TsFiles::getWorkPath, tsFile.getWorkPath())
.eq(TsFiles::getParentId, tsFile.getParentId())
.eq(TsFiles::getIsFile, "FILE")
//地图的四个角的经纬度
.like(TsFiles::getFileName, "maps")
.likeLeft(TsFiles::getFileName, ".txt");
List<TsFiles> listTsFilesTxt = tsFilesMapper.selectList(queryWrapperTxt);
if (listTsFilesTxt == null) {
trajectoriesDto.setTsFilesListTxt(new ArrayList<>());
}else{
for (TsFiles tsFiles1 : listTsFilesTxt) {
//读物文件内容
StorageSourceConfig config = getStorageSourceConfig("filePath", "local", tsTask.getLocalStorageId());
// 1. 路径标准化与安全校验
Path targetPath = validateAndNormalizePath(config.getValue() + tsFiles1.getWorkPath() + tsFiles1.getFileName(), tsTask);
try {
// 高效读取第一行
String firstLine = readFirstLine(targetPath.toString());
// 处理内容
String result = processMapContent(firstLine);
tsFiles1.setFileContent(result);
System.out.println("拼接结果: " + result);
} catch (IOException e) {
System.err.println("文件错误: " + e.getMessage());
} catch (IllegalArgumentException e) {
System.err.println("内容错误: " + e.getMessage());
}
tsFilesListTxt.add(tsFiles1);
}
trajectoriesDto.setTsFilesListTxt(listTsFilesTxt);
}
LambdaQueryWrapper<TsFiles> queryWrapperConfig = new LambdaQueryWrapper<>();
queryWrapperConfig.eq(TsFiles::getTaskId, taskId)
.eq(TsFiles::getNodeId, nodeId)
.eq(TsFiles::getWorkPath, tsFile.getWorkPath())
.eq(TsFiles::getParentId, tsFile.getParentId())
.eq(TsFiles::getIsFile, "FILE")
//配置文件
.like(TsFiles::getFileName, "config")
.likeLeft(TsFiles::getFileName, ".txt");
List<TsFiles> listTsFilesConfig = tsFilesMapper.selectList(queryWrapperConfig);
if (listTsFilesConfig == null) {
trajectoriesDto.setTsFilesListConfig(new ArrayList<>());
}else {
for (TsFiles tsFiles1 : listTsFilesConfig) {
tsFilesListConfig.add(tsFiles1);
}
trajectoriesDto.setTsFilesListConfig(listTsFilesConfig);
}
//3:返回对应的数据
return trajectoriesDto;
} catch (Exception e) {
LOGGER.error("未知异常:{}", e.getMessage(), e);
throw new RuntimeException("系统错误,请联系管理员");
} finally {
TableNameContextHolder.clear();
}
}
private static String readFirstLine(String filePath) throws IOException {
Path path = Paths.get(filePath);
if (!Files.exists(path)) {
throw new IOException("文件不存在: " + filePath);
}
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
return reader.readLine(); // 可能返回 null
}
}
private static String processMapContent(String content) {
if (content == null || content.trim().isEmpty()) {
throw new IllegalArgumentException("内容为空");
}
// 分割字符串处理任意空白
String[] parts = content.trim().split("\\s+");
if (parts.length < 4) {
throw new IllegalArgumentException("需要至少4个坐标值");
}
// 高效拼接
return String.format("%s,%s,%s,%s", parts[0], parts[1], parts[2], parts[3]);
}
}