From 6458f63810175db410341d3104d467c14bbfc2fd Mon Sep 17 00:00:00 2001
From: CMM <2198256324@qq.com>
Date: Thu, 14 Nov 2024 10:17:09 +0800
Subject: [PATCH] =?UTF-8?q?=E9=9B=B7=E8=BE=BE=E6=95=B0=E6=8D=AE=E5=AF=B9?=
=?UTF-8?q?=E6=8E=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../generator/config/GeneratorCodeConfig.java | 4 +-
.../carapi/gps/task/GpsFullDataPullTask.java | 9 +-
.../carapi/gps/task/GpsRealTimeDataPullTask.java | 11 +-
.../carapi/radar/client/RadarHandler.java | 68 ++-
.../carapi/radar/constant/RadarCarTypeEnum.java | 68 +++
.../carapi/radar/constant/RadarDataTypeEnum.java | 66 +++
.../carapi/radar/controller/RadarController.java | 39 +-
.../carapi/radar/helper/RadarDataHelper.java | 482 +++++++++++++++++++++
.../carapi/radar/manage/RadarManage.java | 273 ++++++++++--
.../radar/mapper/RadarOriginalDataMapper.java | 16 +
.../radar/mapper/RadarOriginalDataMapper.xml | 5 +
.../carapi/radar/model/entity/RadarData.java | 35 +-
.../radar/model/entity/RadarOriginalData.java | 45 ++
.../carapi/radar/model/vo/RadarDataVO.java | 53 +++
.../carapi/radar/model/vo/RadarObjectDataVO.java | 38 ++
.../radar/model/vo/RadarTrafficFlowDataVO.java | 32 ++
.../radar/properties/RadarDataTaskProperties.java | 40 +-
.../carapi/radar/server/MultiRadarServer.java | 35 +-
.../radar/service/IRadarOriginalDataService.java | 16 +
.../service/impl/RadarOriginalDataServiceImpl.java | 20 +
.../carapi/radar/task/RadarDataTask.java | 32 +-
.../radar/task/RadarRealTimeDataUpdateTask.java | 240 ++++++++++
.../road/controller/RoadMonitorController.java | 30 +-
.../carapi/road/manage/RoadMonitorManage.java | 115 ++++-
.../carapi/road/model/OverspeedPositionInfo.java | 17 +
.../road/model/entity/RoadBehaviorAnalysis.java | 10 +-
.../road/model/req/RoadMonitorHandleReq.java | 26 ++
.../carapi/road/model/req/RoadMonitorReq.java | 15 +-
.../carapi/road/model/req/VideoDownloadReq.java | 3 +
.../carapi/road/model/vo/RoadDangerBehaviorVO.java | 10 +-
.../carapi/road/model/vo/VehicleGpsDataVO.java | 39 ++
.../carapi/road/task/RoadMonitorDataPullTask.java | 36 +-
.../src/main/resources/application-dev.yml | 18 +-
.../src/main/resources/security/auth-dev.yml | 3 +-
.../com/ningdatech/carapi/radar/RadarTest.java | 437 +++++++++++++++++++
picture/1.jpg | Bin 0 -> 285033 bytes
picture/2.jpg | Bin 0 -> 285033 bytes
picture/3.jpg | Bin 0 -> 285033 bytes
38 files changed, 2191 insertions(+), 195 deletions(-)
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/constant/RadarCarTypeEnum.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/constant/RadarDataTypeEnum.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/helper/RadarDataHelper.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/mapper/RadarOriginalDataMapper.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/mapper/RadarOriginalDataMapper.xml
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/entity/RadarOriginalData.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarDataVO.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarObjectDataVO.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarTrafficFlowDataVO.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/service/IRadarOriginalDataService.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/service/impl/RadarOriginalDataServiceImpl.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/task/RadarRealTimeDataUpdateTask.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/OverspeedPositionInfo.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/RoadMonitorHandleReq.java
create mode 100644 ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/vo/VehicleGpsDataVO.java
create mode 100644 ningda-yw-api/src/test/java/com/ningdatech/carapi/radar/RadarTest.java
create mode 100644 picture/1.jpg
create mode 100644 picture/2.jpg
create mode 100644 picture/3.jpg
diff --git a/ningda-generator/src/main/java/com/ningdatech/generator/config/GeneratorCodeConfig.java b/ningda-generator/src/main/java/com/ningdatech/generator/config/GeneratorCodeConfig.java
index 02ec2a6..258b697 100644
--- a/ningda-generator/src/main/java/com/ningdatech/generator/config/GeneratorCodeConfig.java
+++ b/ningda-generator/src/main/java/com/ningdatech/generator/config/GeneratorCodeConfig.java
@@ -22,7 +22,7 @@ public class GeneratorCodeConfig {
private static final String URL = "jdbc:mysql://47.98.125.47:3306/nd-yw-road?serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true&useSSL=false";
private static final String USER_NAME = "root";
- private static final String PASSWORD = "NingdaKeji123!";
+ private static final String PASSWORD = "Ndkj@1104";
private static void generate(String author, String packageName, String path, String... tableNames) {
FastAutoGenerator.create(URL, USER_NAME, PASSWORD)
@@ -59,7 +59,7 @@ public class GeneratorCodeConfig {
public static void main(String[] args) {
//generate("PoffyZhang", "car.monitor",PATH_ZPF, "nd_vehicle_security_monitor");
//generate("WendyYang", "irs",PATH_YYD, "nd_drivers_license");
- generate("CMM", "test",PATH_CMM, "nd_road_behavior_analysis");
+ generate("CMM", "test",PATH_CMM, "nd_radar_original_data");
}
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/gps/task/GpsFullDataPullTask.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/gps/task/GpsFullDataPullTask.java
index d993979..0f2c7a2 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/gps/task/GpsFullDataPullTask.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/gps/task/GpsFullDataPullTask.java
@@ -88,7 +88,14 @@ public class GpsFullDataPullTask {
log.error("下载路径请求失败!");
return;
}
- JSONObject object = JSONObject.parseObject(response);
+ log.info("Response from server: {}", response);
+ JSONObject object = null;
+ try {
+ object = JSONObject.parseObject(response);
+ } catch (Exception e) {
+ log.error("数据拉取失败! {}",e.getMessage());
+ return;
+ }
log.info("Response from server: {}", object);
String downloadUrl = object.getString("data");
if (StringUtils.isBlank(downloadUrl)){
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/gps/task/GpsRealTimeDataPullTask.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/gps/task/GpsRealTimeDataPullTask.java
index 65dfd6b..0805f39 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/gps/task/GpsRealTimeDataPullTask.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/gps/task/GpsRealTimeDataPullTask.java
@@ -99,8 +99,15 @@ public class GpsRealTimeDataPullTask {
log.error("下载路径请求失败!");
return;
}
- JSONObject object = JSONObject.parseObject(response);
- System.out.println("Response from server: " + response);
+ log.info("Response from server: {}", response);
+ JSONObject object = null;
+ try {
+ object = JSONObject.parseObject(response);
+ } catch (Exception e) {
+ log.error("数据拉取失败! {}",e.getMessage());
+ return;
+ }
+ log.info("Response from server: {}", object);
String downloadUrl = object.getString("data");
if (downloadUrl == null){
log.error("下载路径请求失败!");
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/client/RadarHandler.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/client/RadarHandler.java
index 7248ceb..14fe594 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/client/RadarHandler.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/client/RadarHandler.java
@@ -3,13 +3,24 @@ package com.ningdatech.carapi.radar.client;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import cn.hutool.socket.SocketUtil;
+import com.google.common.collect.Lists;
+import com.ningdatech.carapi.radar.helper.RadarDataHelper;
import com.ningdatech.carapi.radar.model.entity.RadarData;
import com.ningdatech.carapi.radar.service.IRadarDataService;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
@@ -22,50 +33,63 @@ public class RadarHandler implements Runnable{
private final Socket clientSocket;
private final IRadarDataService radarDataService;
private final String radarIp;
+ private final RadarDataHelper radarDataHelper;
- public RadarHandler(Socket socket, IRadarDataService radarDataService, String radarIp) {
+ public RadarHandler(Socket socket, IRadarDataService radarDataService, String radarIp, RadarDataHelper radarDataHelper) {
this.clientSocket = socket;
this.radarDataService = radarDataService;
this.radarIp = radarIp;
+ this.radarDataHelper = radarDataHelper;
}
@Override
public void run() {
+ ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
// 获取输入流以读取雷达发送的数据
try {
- DataInputStream dataInputStream = new DataInputStream(clientSocket.getInputStream());
+ // 从接入数据 开始 持续接收1分钟的数据 接收完成后 关闭连接
+ // 设置1分钟后关闭连接
+ executor.schedule(() -> {
+ try {
+ clientSocket.close();
+ } catch (IOException e) {
+ log.error("Error closing client socket: ", e);
+ }
+ }, 1, TimeUnit.MINUTES);
+
+ InputStream dataInputStream = clientSocket.getInputStream();
// 开启保活选项
clientSocket.setKeepAlive(true);
- byte[] allData = readAllBytesFromDataInputStream(dataInputStream);
- // 处理从雷达接收到的数据
- if (allData.length > 0) {
- String dataString = new String(allData, StandardCharsets.UTF_8);
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ // 循环读取数据,直到Socket被关闭
+ while (!clientSocket.isClosed() && (bytesRead = dataInputStream.read(buffer)) != -1) {
+ // 处理读取到的数据
+ String dataString = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
log.info("Received: {}", dataString);
RadarData radarData = new RadarData();
- radarData.setRadarIp(radarIp);
- radarData.setData(dataString);
+ radarData.setArmIp(radarIp);
+ // 解析数据
radarData.setCreateOn(LocalDateTime.now());
radarData.setUpdateOn(LocalDateTime.now());
radarDataService.save(radarData);
}
- // 关闭当前周期的连接
- clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
+ }finally {
+ // 关闭ScheduledExecutorService
+ executor.shutdown();
+ try {
+ if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+ executor.shutdownNow();
+ }
+ } catch (InterruptedException e) {
+ executor.shutdownNow();
+ }
}
}
- private byte[] readAllBytesFromDataInputStream(DataInputStream dis) throws IOException {
- // 使用 ByteArrayOutputStream 来收集所有读取的字节
- try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
- // 使用一个合适的缓冲区大小
- byte[] data = new byte[4096];
- int nRead;
- while ((nRead = dis.read(data, 0, data.length)) != -1) {
- buffer.write(data, 0, nRead);
- }
- buffer.flush();
- return buffer.toByteArray();
- }
+ private void processData(String dataString, RadarData radarData) {
+ // 校验包头、包尾
}
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/constant/RadarCarTypeEnum.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/constant/RadarCarTypeEnum.java
new file mode 100644
index 0000000..aa25adb
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/constant/RadarCarTypeEnum.java
@@ -0,0 +1,68 @@
+package com.ningdatech.carapi.radar.constant;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @return
+ * @author CMM
+ * @since 2022/12/20 14:10
+ */
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@ApiModel(value = "RadarCarTypeEnum", description = "雷达车辆类型-枚举")
+public enum RadarCarTypeEnum {
+ /**
+ * 自行车情况
+ */
+ NON_MOTOR(1, "非机动车"),
+ CAR(2, "小型车辆"),
+ BUS(3, "中大型车辆"),
+ UNKNOWN(4, "未知");
+
+ private Integer code;
+ private String desc;
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public void setDesc(String desc) {
+ this.desc = desc;
+ }
+
+ public static String getDescByCode(Integer code) {
+ if(Objects.isNull(code)){
+ return StringUtils.EMPTY;
+ }
+ for (RadarCarTypeEnum t : RadarCarTypeEnum.values()) {
+ if (code.equals(t.getCode())) {
+ return t.desc;
+ }
+ }
+ return StringUtils.EMPTY;
+ }
+
+ public static Integer getCodeByDesc(String desc) {
+ if(StringUtils.isBlank(desc)){
+ return null;
+ }
+ for (RadarCarTypeEnum t : RadarCarTypeEnum.values()) {
+ if (desc.equals(t.getDesc())) {
+ return t.code;
+ }
+ }
+ return null;
+ }
+
+ public boolean eq(String val) {
+ return this.name().equals(val);
+ }
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/constant/RadarDataTypeEnum.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/constant/RadarDataTypeEnum.java
new file mode 100644
index 0000000..32c6800
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/constant/RadarDataTypeEnum.java
@@ -0,0 +1,66 @@
+package com.ningdatech.carapi.radar.constant;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * @return
+ * @author CMM
+ * @since 2022/12/20 14:10
+ */
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@ApiModel(value = "RadarDataTypeEnum", description = "雷达数据类型-枚举")
+public enum RadarDataTypeEnum {
+ /**
+ * 自行车情况
+ */
+ OBJECT_DATA(1, "目标数据"),
+ TRAFFIC_DATA(2, "流量数据");
+
+ private Integer code;
+ private String desc;
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public void setDesc(String desc) {
+ this.desc = desc;
+ }
+
+ public static String getDescByCode(Integer code) {
+ if(Objects.isNull(code)){
+ return StringUtils.EMPTY;
+ }
+ for (RadarDataTypeEnum t : RadarDataTypeEnum.values()) {
+ if (code.equals(t.getCode())) {
+ return t.desc;
+ }
+ }
+ return StringUtils.EMPTY;
+ }
+
+ public static Integer getCodeByDesc(String desc) {
+ if(StringUtils.isBlank(desc)){
+ return null;
+ }
+ for (RadarDataTypeEnum t : RadarDataTypeEnum.values()) {
+ if (desc.equals(t.getDesc())) {
+ return t.code;
+ }
+ }
+ return null;
+ }
+
+ public boolean eq(String val) {
+ return this.name().equals(val);
+ }
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/controller/RadarController.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/controller/RadarController.java
index d520b06..e94a26b 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/controller/RadarController.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/controller/RadarController.java
@@ -1,17 +1,20 @@
package com.ningdatech.carapi.radar.controller;
import com.ningdatech.carapi.radar.manage.RadarManage;
+import com.ningdatech.carapi.radar.model.vo.RadarObjectDataVO;
+import com.ningdatech.carapi.radar.model.vo.RadarTrafficFlowDataVO;
+import com.ningdatech.carapi.road.model.req.RoadMonitorReq;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import com.ningdatech.carapi.common.util.OssUtils;
-
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import java.util.List;
+
/**
*
* 前端控制器
@@ -29,9 +32,33 @@ public class RadarController {
private final RadarManage radarManage;
- @ApiOperation(value = "雷达监听连接测试", notes = "雷达监听连接测试")
- @GetMapping("/get-radar-data")
- public String getRadarData() {
- return radarManage.getRadarData();
+ @ApiOperation(value = "客户端-雷达监听连接测试", notes = "客户端-雷达监听连接测试")
+ @GetMapping("/get-radar-data-as-client")
+ public String getRadarDataAsClient() {
+ return radarManage.getRadarDataAsClient();
+ }
+
+ @ApiOperation(value = "服务端-雷达监听连接测试", notes = "服务端-雷达监听连接测试")
+ @GetMapping("/get-radar-data-as-server")
+ public String getRadarDataAsServer() {
+ return radarManage.getRadarDataAsServer();
+ }
+
+ @ApiOperation(value = "工作台雷达交通流量数据", notes = "工作台雷达交通流量数据")
+ @GetMapping("/workbench/radar-traffic-flow-data")
+ public RadarTrafficFlowDataVO workbenchRadarTrafficFlowData(RoadMonitorReq req) {
+ return radarManage.workbenchRadarTrafficFlowData(req);
+ }
+
+ @ApiOperation(value = "道路监控驾驶舱雷达交通流量数据", notes = "道路监控驾驶舱雷达交通流量数据")
+ @GetMapping("/radar-traffic-flow-data")
+ public RadarTrafficFlowDataVO radarTrafficFlowData(RoadMonitorReq req) {
+ return radarManage.radarTrafficFlowData(req);
+ }
+
+ @ApiOperation(value = "道路监控驾驶舱雷达目标轨迹数据", notes = "道路监控驾驶舱雷达目标轨迹数据")
+ @GetMapping("/radar-object-data")
+ public List radarObjectData(RoadMonitorReq req) {
+ return radarManage.radarObjectData(req);
}
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/helper/RadarDataHelper.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/helper/RadarDataHelper.java
new file mode 100644
index 0000000..d7b7fe0
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/helper/RadarDataHelper.java
@@ -0,0 +1,482 @@
+package com.ningdatech.carapi.radar.helper;
+
+import com.google.common.collect.Lists;
+import com.ningdatech.carapi.radar.model.vo.RadarDataVO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author CMM
+ * @since 2024/11/08 10:15
+ */
+@Component
+@RequiredArgsConstructor
+public class RadarDataHelper {
+
+ // 将16进制字符串转换为字节数组
+ public byte[] hexStringToByteArray(String s) {
+ // 确保字符串长度为偶数
+ if (s.length() % 2 != 0) {
+ // 如果长度为奇数,移除最后一个字符
+ s = s.substring(0, s.length() - 1);
+ }
+ int len = s.length();
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ // 如果输入的字符不是16进制字符,跳过
+ if (!Character.isDigit(s.charAt(i)) && !Character.isLetter(s.charAt(i))) {
+ continue;
+ }
+ data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ + Character.digit(s.charAt(i+1), 16));
+ }
+ return data;
+ }
+
+ // 以16进制字节数组解析数据帧并提取指定位置指定字节数的数据
+ public byte[] extractHexData(String hexFrame, int dataOffset, int dataLength) {
+ byte[] frameBytes = hexStringToByteArray(hexFrame);
+ // 检查数据长度是否超出帧的范围
+ if (dataOffset + dataLength > frameBytes.length) {
+ throw new IllegalArgumentException("Data length exceeds frame boundary");
+ }
+ // 提取数据
+ byte[] extractedData = new byte[dataLength];
+ System.arraycopy(frameBytes, dataOffset, extractedData, 0, dataLength);
+ return extractedData;
+ }
+
+ // 在字节数组中查找特定模式
+ public int findPattern(byte[] array, byte[] pattern) {
+ for (int i = 0; i < array.length - pattern.length + 1; i++) {
+ boolean found = true;
+ for (int j = 0; j < pattern.length; j++) {
+ if (array[i + j] != pattern[j]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ // 将字节数组转换为16进制字符串
+ public String byteArrayToHexString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ // 过滤掉EF00
+ if (b == (byte) 0xEF || b == (byte) 0x00){
+ continue;
+ }
+ sb.append(String.format("%02X", b));
+ }
+ return sb.toString();
+ }
+
+ // 将字节数组转换为16进制字符串
+ public String byteArrayToHexStringAll(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(String.format("%02X", b));
+ }
+ return sb.toString();
+ }
+
+ public void processRadarData(String data) {
+ String head = "55aa55bb";
+ String tail = "55cc55dd";
+
+ // 解析上面的数据帧data 包头是head 包尾是tail 从包头到包尾分段取出数据 放入一个字符串列表中 等待解析
+ List segments = Lists.newArrayList();
+ int headIndex = data.indexOf(head);
+ while (headIndex != -1) {
+ int tailIndex = data.indexOf(tail, headIndex + head.length());
+ if (tailIndex != -1) {
+ // 提取从包头到包尾的数据段
+ String segment = data.substring(headIndex + head.length(), tailIndex);
+ segments.add(segment);
+ // 移动到下一个包头的位置
+ headIndex = data.indexOf(head, tailIndex + tail.length());
+ } else {
+ // 没有找到包尾,结束循环
+ break;
+ }
+ }
+
+ // 遍历这些数据帧 解析其中携带的数据
+ for (String segment : segments) {
+ System.out.println(segment);
+
+ //// 获取数据长度 2字节 4字符 判断数据包长度
+ byte[] dataLengthData = extractHexData(segment, 0, 2);
+ String dataLengthHexString = byteArrayToHexStringAll(dataLengthData);
+ // 将16进制字符串转换为字节数组
+ byte[] lengthBytes = new byte[2];
+ // 高8位
+ lengthBytes[0] = (byte) Integer.parseInt(dataLengthHexString.substring(0, 2), 16);
+ // 低8位
+ lengthBytes[1] = (byte) Integer.parseInt(dataLengthHexString.substring(2, 4), 16);
+
+ // 将字节数组转换为16位整数,注意网络字节序是大端序
+ int byteLength = ((lengthBytes[0] & 0xFF) << 8) | (lengthBytes[1] & 0xFF);
+ // 打印结果
+ System.out.println("数据长度: " + byteLength);
+
+ // 获取数据帧类型 1字节 2字符 判断是目标轨迹数据还是交通流量数据
+ byte[] objectTypeData = extractHexData(segment, 2, 1);
+ String objectTypeHexString = byteArrayToHexString(objectTypeData);
+ System.out.println("数据帧类型:" + objectTypeHexString);
+
+ // 获取设备编号 20字节 40字符 判断是哪个雷达的发送的数据
+ byte[] deviceIdData = extractHexData(segment, 4, 16);
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < deviceIdData.length; i++) {
+ byte b = deviceIdData[i];
+ // 16进制数 "32"
+ String hexString = String.format("%02X", b);
+ // 将16进制字符串转换为十进制整数
+ int decimalValue = Integer.parseInt(hexString, 16);
+ // 将十进制整数转换为对应的ASCII字符
+ char asciiChar = (char) decimalValue;
+ // 如果不为0则添加到字符串中
+ if (decimalValue != 0) {
+ builder.append(asciiChar);
+ }
+ }
+ System.out.println("设备编号:" + builder);
+
+
+ byte[] timestampData = extractHexData(segment, 24, 8);
+ String timestampHexString = byteArrayToHexString(timestampData);
+ System.out.println("时间戳:" + timestampHexString);
+ String localDateTime = getLocalDateTime(segment, timestampData);
+ System.out.println("时间戳:" + localDateTime);
+ // 获取数据
+ // 用获取到的数据长度 减去数据长度(2字节)、数据帧类型(1字节)、校验和(1字节)、数据编号)、时间戳(8字节)、包头包尾(8字节)
+ int dataLength = byteLength - 2 - 1 - 1 - 20 - 8;
+ segment = segment.substring(64);
+ System.out.println("总数据:" + segment);
+
+ // 如果是目标轨迹数据
+ if ("01".equals(objectTypeHexString)) {
+ if (dataLength >= 36) {
+ byte[] dataBytes = hexStringToByteArray(segment);
+ // 36个字节为一组数据 进行分组解析
+ for (int i = 0; i < dataBytes.length; i += 36) {
+ byte[] dataSegment = Arrays.copyOfRange(dataBytes, i, i + 36);
+ String dataSegmentHexString = byteArrayToHexStringAll(dataSegment);
+ System.out.println("数据:" + dataSegmentHexString);
+ // 获取目标所属车道
+ byte[] targetLaneData = extractHexData(dataSegmentHexString, 2, 1);
+ String targetLaneHexString = byteArrayToHexStringAll(targetLaneData);
+ // 将16进制数转换为10进制数
+ int targetLane = Integer.parseInt(targetLaneHexString, 16);
+ System.out.println("目标所属车道:" + targetLane);
+ // 获取目标类型
+ byte[] carTypeData = extractHexData(dataSegmentHexString, 3, 1);
+ String carTypeHexString = byteArrayToHexStringAll(carTypeData);
+ System.out.println("目标类型:" + carTypeHexString);
+ // 获取目标速度
+ byte[] targetSpeedData = extractHexData(dataSegmentHexString, 20, 2);
+ String targetSpeedHexString = byteArrayToHexStringAll(targetSpeedData);
+ double targetSpeed = Integer.parseInt(targetSpeedHexString, 16) * 0.01;
+ System.out.println("目标速度:" + targetSpeed);
+ // 获取目标经度
+ byte[] targetLongitudeData = extractHexData(dataSegmentHexString, 28, 4);
+ String targetLongitudeHexString = byteArrayToHexStringAll(targetLongitudeData);
+ // 将十六进制字符串转换为long类型的数字
+ long targetLongitudeLongValue = Long.parseLong(targetLongitudeHexString, 16);
+ // 将long类型的数字转换为double类型的数字
+ double targetLongitude = (double) targetLongitudeLongValue / 10000000;
+ System.out.println("目标经度:" + targetLongitude);
+ // 获取目标纬度
+ byte[] targetLatitudeData = extractHexData(dataSegmentHexString, 32, 4);
+ String targetLatitudeHexString = byteArrayToHexStringAll(targetLatitudeData);
+
+ // 将十六进制字符串转换为long类型的数字
+ long targetLatitudeLongValue = Long.parseLong(targetLatitudeHexString, 16);
+ // 将long类型的数字转换为double类型的数字
+ double targetLatitude = (double) targetLatitudeLongValue / 10000000;
+ System.out.println("目标纬度:" + targetLatitude);
+ }
+ }
+ }
+ // 如果是交通流量数据
+ else if ("03".equals(objectTypeHexString)) {
+ if (dataLength >= 22) {
+ byte[] dataBytes = hexStringToByteArray(segment);
+ // 22个字节为一组数据 进行分组解析
+ for (int i = 0; i < dataBytes.length; i += 22) {
+ byte[] dataSegment = Arrays.copyOfRange(dataBytes, i, i + 22);
+ String dataSegmentHexString = byteArrayToHexStringAll(dataSegment);
+ System.out.println("数据:" + dataSegmentHexString);
+ // 获取统计周期
+ byte[] statisticsCycleData = extractHexData(dataSegmentHexString, 0, 2);
+ String statisticsCycleHexString = byteArrayToHexStringAll(statisticsCycleData);
+ // 将16进制数转换为10进制数
+ int statisticsCycle = Integer.parseInt(statisticsCycleHexString, 16);
+ System.out.println("统计周期:" + statisticsCycle);
+ // 获取实时车流量
+ byte[] realTimeTrafficFlowData = extractHexData(dataSegmentHexString, 12, 2);
+ String realTimeTrafficFlowHexString = byteArrayToHexStringAll(realTimeTrafficFlowData);
+ // 将16进制数转换为10进制数
+ int realTimeTrafficFlow = Integer.parseInt(realTimeTrafficFlowHexString, 16);
+ System.out.println("总流量:" + realTimeTrafficFlow);
+ // 获取平均车速
+ byte[] averageSpeedData = extractHexData(dataSegmentHexString, 14, 2);
+ String averageSpeedHexString = byteArrayToHexStringAll(averageSpeedData);
+ double averageSpeed = Integer.parseInt(averageSpeedHexString, 16) * 0.1;
+ System.out.println("平均速度:" + averageSpeed);
+ // 获取最大排队长度
+ byte[] maxQueueLengthData = extractHexData(dataSegmentHexString, 20, 2);
+ String maxQueueLengthHexString = byteArrayToHexStringAll(maxQueueLengthData);
+ // 将16进制数转换为10进制数
+ int maxQueueLength = Integer.parseInt(maxQueueLengthHexString, 16);
+ System.out.println("最大排队长度:" + maxQueueLength);
+ }
+ }
+ }
+ }
+ }
+
+ public List getRadarDataList(String data) {
+
+ List vos = Lists.newArrayList();
+
+ String head = "55aa55bb";
+ String tail = "55cc55dd";
+
+ // 解析上面的数据帧data 包头是head 包尾是tail 从包头到包尾分段取出数据 放入一个字符串列表中 等待解析
+ List segments = Lists.newArrayList();
+ int headIndex = data.indexOf(head);
+ while (headIndex != -1) {
+ int tailIndex = data.indexOf(tail, headIndex + head.length());
+ if (tailIndex != -1) {
+ // 提取从包头到包尾的数据段
+ String segment = data.substring(headIndex + head.length(), tailIndex);
+ segments.add(segment);
+ // 移动到下一个包头的位置
+ headIndex = data.indexOf(head, tailIndex + tail.length());
+ } else {
+ // 没有找到包尾,结束循环
+ break;
+ }
+ }
+
+ // 遍历这些数据帧 解析其中携带的数据
+ for (String segment : segments) {
+ RadarDataVO vo = new RadarDataVO();
+ //// 获取数据长度 2字节 4字符 判断数据包长度
+ byte[] dataLengthData = extractHexData(segment, 0, 2);
+ String dataLengthHexString = byteArrayToHexStringAll(dataLengthData);
+ // 将16进制字符串转换为字节数组
+ byte[] lengthBytes = new byte[2];
+ // 高8位
+ lengthBytes[0] = (byte) Integer.parseInt(dataLengthHexString.substring(0, 2), 16);
+ // 低8位
+ lengthBytes[1] = (byte) Integer.parseInt(dataLengthHexString.substring(2, 4), 16);
+
+ // 将字节数组转换为16位整数,注意网络字节序是大端序
+ int byteLength = ((lengthBytes[0] & 0xFF) << 8) | (lengthBytes[1] & 0xFF);
+
+ // 获取数据帧类型 1字节 2字符 判断是目标轨迹数据还是交通流量数据
+ byte[] objectTypeData = extractHexData(segment, 2, 1);
+ String objectTypeHexString = byteArrayToHexString(objectTypeData);
+
+ // 获取设备编号 20字节 40字符 判断是哪个雷达的发送的数据
+ byte[] deviceIdData = extractHexData(segment, 4, 16);
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < deviceIdData.length; i++) {
+ byte b = deviceIdData[i];
+ // 16进制数 "32"
+ String hexString = String.format("%02X", b);
+ // 将16进制字符串转换为十进制整数
+ int decimalValue = Integer.parseInt(hexString, 16);
+ // 将十进制整数转换为对应的ASCII字符
+ char asciiChar = (char) decimalValue;
+ // 如果不为0则添加到字符串中
+ if (decimalValue != 0) {
+ builder.append(asciiChar);
+ }
+ }
+ vo.setDeviceId(builder.toString());
+
+ byte[] timestampData = extractHexData(segment, 24, 8);
+ String timestampHexString = byteArrayToHexString(timestampData);
+ System.out.println("时间戳:" + timestampHexString);
+ String localDateTime = getLocalDateTime(segment, timestampData);
+ vo.setTimeStamp(localDateTime);
+
+ // 获取数据
+ // 用获取到的数据长度 减去数据长度(2字节)、数据帧类型(1字节)、校验和(1字节)、数据编号)、时间戳(8字节)、包头包尾(8字节)
+ int dataLength = byteLength - 2 - 1 - 1 - 20 - 8;
+ segment = segment.substring(64);
+
+ // 如果是目标轨迹数据
+ if ("01".equals(objectTypeHexString)) {
+ vo.setDataType("1");
+ if (dataLength >= 36) {
+ byte[] dataBytes = hexStringToByteArray(segment);
+ // 36个字节为一组数据 进行分组解析
+ for (int i = 0; i < dataBytes.length; i += 36) {
+ byte[] dataSegment = Arrays.copyOfRange(dataBytes, i, i + 36);
+ String dataSegmentHexString = byteArrayToHexStringAll(dataSegment);
+ // 获取目标所属车道
+ byte[] targetLaneData = extractHexData(dataSegmentHexString, 2, 1);
+ String targetLaneHexString = byteArrayToHexStringAll(targetLaneData);
+ // 将16进制数转换为10进制数
+ int targetLane = Integer.parseInt(targetLaneHexString, 16);
+ vo.setTargetLane(String.valueOf(targetLane));
+ // 获取目标类型
+ byte[] carTypeData = extractHexData(dataSegmentHexString, 3, 1);
+ String carTypeHexString = byteArrayToHexStringAll(carTypeData);
+ vo.setCarType(carTypeHexString);
+ // 获取目标速度
+ byte[] targetSpeedData = extractHexData(dataSegmentHexString, 20, 2);
+ String targetSpeedHexString = byteArrayToHexStringAll(targetSpeedData);
+ double targetSpeed = Integer.parseInt(targetSpeedHexString, 16) * 0.01;
+ vo.setTargetSpeed(String.valueOf(targetSpeed));
+ // 获取目标经度
+ byte[] targetLongitudeData = extractHexData(dataSegmentHexString, 28, 4);
+ String targetLongitudeHexString = byteArrayToHexStringAll(targetLongitudeData);
+ // 将十六进制字符串转换为long类型的数字
+ long targetLongitudeLongValue = Long.parseLong(targetLongitudeHexString, 16);
+ // 将long类型的数字转换为double类型的数字
+ double targetLongitude = (double) targetLongitudeLongValue / 10000000;
+ vo.setTargetLongitude(String.valueOf(targetLongitude));
+ // 获取目标纬度
+ byte[] targetLatitudeData = extractHexData(dataSegmentHexString, 32, 4);
+ String targetLatitudeHexString = byteArrayToHexStringAll(targetLatitudeData);
+ // 将十六进制字符串转换为long类型的数字
+ long targetLatitudeLongValue = Long.parseLong(targetLatitudeHexString, 16);
+ // 将long类型的数字转换为double类型的数字
+ double targetLatitude = (double) targetLatitudeLongValue / 10000000;
+ vo.setTargetLatitude(String.valueOf(targetLatitude));
+ vos.add(vo);
+ }
+ }
+ }
+ // 如果是交通流量数据
+ else if ("03".equals(objectTypeHexString)) {
+ vo.setDataType("3");
+ if (dataLength >= 22) {
+ byte[] dataBytes = hexStringToByteArray(segment);
+ // 22个字节为一组数据 进行分组解析
+ for (int i = 0; i < dataBytes.length; i += 22) {
+ byte[] dataSegment = Arrays.copyOfRange(dataBytes, i, i + 22);
+ String dataSegmentHexString = byteArrayToHexStringAll(dataSegment);
+ // 获取实时车流量
+ byte[] realTimeTrafficFlowData = extractHexData(dataSegmentHexString, 12, 2);
+ String realTimeTrafficFlowHexString = byteArrayToHexStringAll(realTimeTrafficFlowData);
+ // 将16进制数转换为10进制数
+ int realTimeTrafficFlow = Integer.parseInt(realTimeTrafficFlowHexString, 16);
+ vo.setRealTimeTrafficFlow(String.valueOf(realTimeTrafficFlow));
+ // 获取平均车速
+ byte[] averageSpeedData = extractHexData(dataSegmentHexString, 14, 2);
+ String averageSpeedHexString = byteArrayToHexStringAll(averageSpeedData);
+ double averageSpeed = Integer.parseInt(averageSpeedHexString, 16) * 0.1;
+ vo.setAverageCarSpeed(String.valueOf(averageSpeed));
+ // 获取最大排队长度
+ byte[] maxQueueLengthData = extractHexData(dataSegmentHexString, 20, 2);
+ String maxQueueLengthHexString = byteArrayToHexStringAll(maxQueueLengthData);
+ // 将16进制数转换为10进制数
+ int maxQueueLength = Integer.parseInt(maxQueueLengthHexString, 16);
+ vo.setMaxQueueLength(String.valueOf(maxQueueLength));
+ vos.add(vo);
+ }
+ }
+ }
+ }
+ return vos;
+ }
+
+ private String getLocalDateTime(String segment, byte[] timestampData) {
+ // 第一个字节 -> year
+ byte year = timestampData[0];
+ // 将16进制字节转换为整数
+ int yearInt = Integer.parseInt(String.format("%02X", year), 16);
+ // 创建位掩码来提取第0到7位
+ int yearMask = 0xFF;
+ // 使用位掩码提取第0到第7位
+ int extractedYearBits = yearInt & yearMask;
+ // 数据 0~255 对应 2000~2255 year 获取实际的年份
+ int yearValue = extractedYearBits + 2000;
+ System.out.println("年份:" + yearValue);
+
+ // 第二个字节 -> month
+ byte month = timestampData[1];
+ // 将16进制字节转换为整数
+ int monthInt = Integer.parseInt(String.format("%02X", month), 16);
+ // 创建位掩码来提取第0到3位
+ // 1~12 对应 1~12 month
+ int monthMask = 0x0F;
+ // 使用位掩码提取第0到第3位
+ int monthValue = monthInt & monthMask;
+ System.out.println("月份:" + monthValue);
+
+ // 第三个字节 -> day
+ byte day = timestampData[2];
+ // 将16进制字节转换为整数
+ int dayInt = Integer.parseInt(String.format("%02X", day), 16);
+ // 创建位掩码来提取第0到4位
+ // 数据 1~31 对应 1~31 day
+ int dayMask = 0x1F;
+ // 使用位掩码提取第0到第4位
+ // 数据 1~31 对应 1~31 day
+ int dayValue = dayInt & dayMask;
+ System.out.println("天:" + dayValue);
+
+ // 第四个字节 -> hour
+ byte hour = timestampData[3];
+ // 将16进制字节转换为整数
+ int hourInt = Integer.parseInt(String.format("%02X", hour), 16);
+ // 创建位掩码来提取第0到4位
+ int hourMask = 0x1F;
+ // 使用位掩码提取第0到第4位
+ // 数据 0~31 对应 0~23 hour
+ int hourValue = hourInt & hourMask;
+ System.out.println("小时:" + hourValue);
+
+ // 第五个字节 -> minute
+ byte minute = timestampData[4];
+ // 将16进制字节转换为整数
+ int minuteInt = Integer.parseInt(String.format("%02X", minute), 16);
+ // 创建位掩码来提取第0到5位
+ int minuteMask = 0x3F;
+ // 使用位掩码提取第0到第5位
+ // 数据 0~63 对应 0~59 minute
+ int minuteValue = minuteInt & minuteMask;
+ System.out.println("分钟:" + minuteValue);
+
+ // 第六个字节 -> second
+ byte second = timestampData[5];
+ // 将16进制字节转换为整数
+ int secondInt = Integer.parseInt(String.format("%02X", second), 16);
+ // 创建位掩码来提取第0到5位
+ int secondMask = 0x3F;
+ // 使用位掩码提取第0到第5位
+ // 数据 0~63 对应 0~59 second
+ int secondValue = secondInt & secondMask;
+ System.out.println("秒:" + secondValue);
+ // 第7、8个字节-> millisecond
+ // 把第7和第8个字节拼接成16进制字符串 再转换为整数
+ byte[] millisecondData = extractHexData(segment, 30, 2);
+ String millisecondHexString = byteArrayToHexString(millisecondData);
+ int millisecondValue = Integer.parseInt(millisecondHexString, 16);
+ // 创建位掩码来提取第0到9位
+ int millisecondMask = 0x3FF;
+ // 使用位掩码提取第0到第9位
+ millisecondValue = millisecondValue & millisecondMask;
+ // 数据 0~999 对应 0~999 毫秒
+ System.out.println("毫秒:" + millisecondValue);
+ // 拼接成LocalDateTime 类型的数据
+ String localDateTime = yearValue + "-" + monthValue + "-" + dayValue + " " + hourValue + ":" + minuteValue + ":" + secondValue;
+ return localDateTime;
+ }
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/manage/RadarManage.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/manage/RadarManage.java
index cc4d727..9139d18 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/manage/RadarManage.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/manage/RadarManage.java
@@ -1,16 +1,37 @@
package com.ningdatech.carapi.radar.manage;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
import java.io.IOException;
-import java.net.InetAddress;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
-import org.apache.log4j.net.SocketServer;
+import cn.hutool.socket.SocketUtil;
+import com.ningdatech.carapi.radar.helper.RadarDataHelper;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.google.common.collect.Lists;
+import com.ningdatech.basic.exception.BizException;
+import com.ningdatech.carapi.radar.constant.RadarCarTypeEnum;
+import com.ningdatech.carapi.radar.constant.RadarDataTypeEnum;
+import com.ningdatech.carapi.radar.model.entity.RadarData;
+import com.ningdatech.carapi.radar.model.vo.RadarObjectDataVO;
+import com.ningdatech.carapi.radar.model.vo.RadarTrafficFlowDataVO;
+import com.ningdatech.carapi.radar.service.IRadarDataService;
+import com.ningdatech.carapi.road.model.req.RoadMonitorReq;
+
+import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -25,47 +46,219 @@ import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
public class RadarManage {
- public String getRadarData() {
- try {
- // 192.168.6.42
- ServerSocket serverSocket = new ServerSocket(13000);
- // 设置为0表示无限等待,可以根据需要设置超时时间
- serverSocket.setSoTimeout(6000);
- System.out.println("ServerSocket started on port: " + 13000);
- log.info("ServerSocket started on port: {}", 13000);
- Socket clientSocket = serverSocket.accept();
- System.out.println("Connect Successfully!");
- log.info("Connect Successfully!");
-
- DataInputStream dataInputStream = new DataInputStream(clientSocket.getInputStream());
- // 开启保活选项
- clientSocket.setKeepAlive(true);
- byte[] allData;
- while (!clientSocket.isClosed()) {
- // 使用 ByteArrayOutputStream 来收集所有读取的字节
- try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
- // 使用一个合适的缓冲区大小
- byte[] data = new byte[4096];
- int nRead;
- while ((nRead = dataInputStream.read(data, 0, data.length)) != -1 && buffer.size() < 4096) {
- buffer.write(data, 0, nRead);
- }
- buffer.flush();
- allData = buffer.toByteArray();
+ private final IRadarDataService radarDataService;
+ private final RadarDataHelper radarDataHelper;
+
+ public String getRadarDataAsClient() {
+ // 盒子ip地址:192.168.6.42 --> 192.168.6.40(黄山隧道义乌来向雷达)、192.168.6.43(黄山隧道义乌去向雷达)
+ ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+ // 默认结果
+ String result = "接收失败!";
+ String ip = "192.168.6.42";
+ // String ip = "192.168.2.249";
+ try (Socket socket = SocketUtil.connect(ip, 13000)) {
+ log.info("Client connected to server on port: {}", 13000);
+ InputStream inputStream = socket.getInputStream();
+ if (Objects.nonNull(inputStream)) {
+ // 读取输入流中的1024个字节
+ byte[] buffer = new byte[1024];
+ if (inputStream.available() > 0) {
+ int len = inputStream.read(buffer);
+ // 处理读取到的数据
+ byte[] data = Arrays.copyOf(buffer, len);
+ log.info("Received: {}", Arrays.toString(data));
+ log.info("Connect Successfully!");
+ }else {
+ return "流数据为空!";
}
- // 处理从雷达接收到的数据
- if (allData.length > 0) {
- String dataString = new String(allData, StandardCharsets.UTF_8);
- System.out.println("Received: " + dataString);
- log.info("Received: {}", dataString);
- break;
+ }else{
+ return result;
+ }
+ // 从接入数据 开始 持续接收1分钟的数据 接收完成后 关闭连接
+ // 设置1分钟后关闭连接
+ executor.schedule(() -> {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ log.error("Error closing client socket: ", e);
}
+ }, 1, TimeUnit.MINUTES);
+
+ // 开启保活选项
+ socket.setKeepAlive(true);
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ // 循环读取数据,直到Socket被关闭
+ while (!socket.isClosed() && (bytesRead = inputStream.read(buffer)) != -1) {
+ // 处理读取到的数据
+ String dataString = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
+ log.info("Received: {}", dataString);
+ radarDataHelper.processRadarData(dataString);
}
- // 关闭当前周期的连接
- clientSocket.close();
+ // 如果循环正常结束,则修改结果
+ result = "接收成功!";
} catch (IOException e) {
return e.getMessage();
+ }finally {
+ // 关闭ScheduledExecutorService
+ executor.shutdown();
+ try {
+ if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+ executor.shutdownNow();
+ }
+ } catch (InterruptedException e) {
+ executor.shutdownNow();
+ }
+ }
+ // 返回最终结果
+ return result;
+ }
+
+ public String getRadarDataAsServer() {
+ // 盒子ip地址:192.168.6.42 --> 192.168.6.40(黄山隧道义乌来向雷达)、192.168.6.43(黄山隧道义乌去向雷达)
+ ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+ // 默认结果
+ String result = "接收失败!";
+ String ip = "192.168.6.42";
+ //String ip = "192.168.2.249";
+ try (ServerSocket serverSocket = new ServerSocket()) {
+ // 设置超时时间
+ serverSocket.setSoTimeout(60000);
+ InetSocketAddress address = new InetSocketAddress(ip, 13000);
+ //InetSocketAddress address = new InetSocketAddress("192.168.2.254", 13000);
+ serverSocket.bind(address);
+
+ // 等待客户端连接
+ System.out.println("等待客户端连接...");
+ Socket socket = serverSocket.accept();
+ System.out.println("客户端已连接!");
+
+ // 读取客户端推送的数据
+ InputStream inputStream = socket.getInputStream();
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = inputStream.read(buffer)) != -1) {
+ // 处理接收到的数据
+ String data = new String(buffer, 0, len);
+ System.out.println("接收到数据:" + data);
+ }
+ result = "接收成功!";
+ } catch (Exception e) {
+ log.error("Error closing server socket: ", e);
+ }
+ // 返回最终结果
+ return result;
+ }
+
+
+ public RadarTrafficFlowDataVO radarTrafficFlowData(RoadMonitorReq req) {
+ RadarTrafficFlowDataVO vo = new RadarTrafficFlowDataVO();
+ String region = req.getRegion();
+ // 雷达区域-ip对应关系
+ Map radarRegionIpMap = getRadarRegionIpMap();
+ // 判断查询的是哪个设备的数据
+ String radarIp = radarRegionIpMap.getOrDefault(region, null);
+ if (StringUtils.isBlank(radarIp)){
+ throw new BizException("查询区域数据不存在!");
+ }
+ // 从雷达数据信息表中获取查询区域最新的一条流量数据
+ RadarData radarData = radarDataService.getOne(Wrappers.lambdaQuery(RadarData.class)
+ .eq(RadarData::getRadarIp, radarIp)
+ .eq(RadarData::getDataType, RadarDataTypeEnum.TRAFFIC_DATA.getCode())
+ .orderByDesc(RadarData::getUpdateOn)
+ .last("limit 1"));
+ if (Objects.nonNull(radarData)) {
+ BeanUtils.copyProperties(radarData, vo);
+ }
+ return vo;
+ }
+
+ private static Map getRadarRegionIpMap() {
+ Map radarRegionIpMap = new HashMap<>();
+ // 黄山隧道东阳去向
+ radarRegionIpMap.put("1", "192.168.6.45");
+ // 黄山隧道东阳来向
+ radarRegionIpMap.put("2", "192.168.6.47");
+ // 黄山隧道义乌去向
+ radarRegionIpMap.put("3", "192.168.6.43");
+ // 黄山隧道义乌来向
+ radarRegionIpMap.put("4", "192.168.6.40");
+ // 何里隧道兰溪方向
+ radarRegionIpMap.put("5", "192.168.10.63");
+ // 何里隧道义乌方向
+ radarRegionIpMap.put("6", "192.168.10.60");
+ return radarRegionIpMap;
+ }
+
+ public List radarObjectData(RoadMonitorReq req) {
+ String region = req.getRegion();
+ // 雷达区域-ip对应关系
+ Map radarRegionIpMap = getRadarRegionIpMap();
+ // 判断查询的是哪个设备的数据
+ String radarIp = radarRegionIpMap.getOrDefault(region, null);
+ if (StringUtils.isBlank(radarIp)){
+ throw new BizException("查询区域数据不存在!");
}
- return null;
+ // 从雷达数据信息表中获取查询区域最近1分钟的目标数据
+ LocalDateTime startTime = LocalDateTime.now().minusMinutes(1);
+ List radarDataList = radarDataService.list(Wrappers.lambdaQuery(RadarData.class)
+ .eq(RadarData::getRadarIp, radarIp)
+ .eq(RadarData::getDataType, RadarDataTypeEnum.OBJECT_DATA.getCode())
+ .ge(RadarData::getUpdateOn, startTime)
+ .orderByDesc(RadarData::getUpdateOn));
+ // 如果查询不到数据,则返回雷达数据表中最新的100条目标轨迹数据
+ if (CollUtil.isEmpty(radarDataList)){
+ radarDataList = radarDataService.list(Wrappers.lambdaQuery(RadarData.class)
+ .eq(RadarData::getRadarIp, radarIp)
+ .eq(RadarData::getDataType, RadarDataTypeEnum.OBJECT_DATA.getCode())
+ .orderByDesc(RadarData::getUpdateOn)
+ .last("limit 100"));
+ }
+ return radarDataList.stream().map(d -> {
+ RadarObjectDataVO vo = new RadarObjectDataVO();
+ // TODO 待接入雷达数据后 转换为中文 当前以原始编号返回
+ vo.setTargetLane(d.getTargetLane());
+ vo.setCarType(RadarCarTypeEnum.getDescByCode(d.getCarType()));
+ vo.setTargetSpeed(d.getTargetSpeed());
+ vo.setTargetLatitude(d.getTargetLatitude());
+ vo.setTargetLongitude(d.getTargetLongitude());
+ vo.setUpdateOn(d.getUpdateOn());
+ return vo;
+ }).collect(Collectors.toList());
+ }
+
+ public RadarTrafficFlowDataVO workbenchRadarTrafficFlowData(RoadMonitorReq req) {
+ RadarTrafficFlowDataVO vo = new RadarTrafficFlowDataVO();
+ // 雷达区域-ip对应关系
+ List radarDataList = getRadarDataList();
+ if (CollUtil.isEmpty(radarDataList)){
+ vo.setRealTimeTrafficFlow("0");
+ return vo;
+ }
+ // 计算实时流量的和
+ List list = radarDataList.stream().map(RadarData::getRealTimeTrafficFlow).collect(Collectors.toList());
+ List decimals = list.stream().map(d -> BigDecimal.valueOf(Double.parseDouble(d))).collect(Collectors.toList());
+ BigDecimal total = decimals.stream().reduce(BigDecimal.ZERO, BigDecimal::add).stripTrailingZeros();
+ vo.setRealTimeTrafficFlow(String.valueOf(total));
+ return vo;
+ }
+
+ private List getRadarDataList() {
+ Map radarRegionIpMap = getRadarRegionIpMap();
+ List radarIpList = new ArrayList<>(radarRegionIpMap.values());
+
+ // 从雷达数据信息表中获取每个区域最新的一条流量数据
+ List radarDataList = Lists.newArrayList();
+ radarIpList.forEach(radarIp -> {
+ RadarData radarData = radarDataService.getOne(Wrappers.lambdaQuery(RadarData.class)
+ .eq(RadarData::getRadarIp, radarIp)
+ .eq(RadarData::getDataType, RadarDataTypeEnum.TRAFFIC_DATA.getCode())
+ .orderByDesc(RadarData::getUpdateOn)
+ .last("limit 1"));
+ if (Objects.nonNull(radarData)) {
+ radarDataList.add(radarData);
+ }
+ });
+ return radarDataList;
}
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/mapper/RadarOriginalDataMapper.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/mapper/RadarOriginalDataMapper.java
new file mode 100644
index 0000000..7a7bbca
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/mapper/RadarOriginalDataMapper.java
@@ -0,0 +1,16 @@
+package com.ningdatech.carapi.radar.mapper;
+
+import com.ningdatech.carapi.radar.model.entity.RadarOriginalData;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author CMM
+ * @since 2024-11-13
+ */
+public interface RadarOriginalDataMapper extends BaseMapper {
+
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/mapper/RadarOriginalDataMapper.xml b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/mapper/RadarOriginalDataMapper.xml
new file mode 100644
index 0000000..71a0824
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/mapper/RadarOriginalDataMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/entity/RadarData.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/entity/RadarData.java
index 542881b..2c707c9 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/entity/RadarData.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/entity/RadarData.java
@@ -28,15 +28,46 @@ public class RadarData implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
- @ApiModelProperty("雷达推送的数据包")
- private String data;
+ @ApiModelProperty("推送数据的盒子ip")
+ private String armIp;
@ApiModelProperty("推送数据的雷达ip")
private String radarIp;
+ @ApiModelProperty("数据类型 1 目标数据、2 流量数据")
+ private Integer dataType;
+
@ApiModelProperty("创建时间")
private LocalDateTime createOn;
@ApiModelProperty("更新时间")
private LocalDateTime updateOn;
+
+
+ //---------- 目标轨迹数据 ----------//
+ @ApiModelProperty("目标所属车道")
+ private String targetLane;
+
+ @ApiModelProperty("车辆类型")
+ private Integer carType;
+
+ @ApiModelProperty("目标速度")
+ private String targetSpeed;
+
+ @ApiModelProperty("目标经度")
+ private String targetLongitude;
+
+ @ApiModelProperty("目标纬度")
+ private String targetLatitude;
+
+ //---------- 交通流量数据 ----------//
+ @ApiModelProperty("实时车流量")
+ private String realTimeTrafficFlow;
+
+ @ApiModelProperty("平均车速")
+ private String averageCarSpeed;
+
+ @ApiModelProperty("最大排队长度")
+ private String maxQueueLength;
+
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/entity/RadarOriginalData.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/entity/RadarOriginalData.java
new file mode 100644
index 0000000..540baa9
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/entity/RadarOriginalData.java
@@ -0,0 +1,45 @@
+package com.ningdatech.carapi.radar.model.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ *
+ *
+ *
+ *
+ * @author CMM
+ * @since 2024-11-13
+ */
+@TableName("nd_radar_original_data")
+@ApiModel(value = "RadarOriginalData对象", description = "")
+@Data
+public class RadarOriginalData implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty("主键")
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty("盒子推送的雷达数据")
+ private String data;
+
+ @ApiModelProperty("盒子对应的ip")
+ private String armIp;
+
+ @ApiModelProperty("数据类型")
+ private String dataType;
+
+ @ApiModelProperty("创建时间")
+ private LocalDateTime createOn;
+
+ @ApiModelProperty("更新时间")
+ private LocalDateTime updateOn;
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarDataVO.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarDataVO.java
new file mode 100644
index 0000000..d3eb5d1
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarDataVO.java
@@ -0,0 +1,53 @@
+package com.ningdatech.carapi.radar.model.vo;
+
+import java.time.LocalDateTime;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author CMM
+ * @since 2024/10/23 10:49
+ */
+@Data
+@ApiModel(description = "道路监控驾驶舱雷达流量数据-VO")
+@NoArgsConstructor
+@AllArgsConstructor
+public class RadarDataVO {
+
+ @ApiModelProperty("设备编号")
+ private String deviceId;
+
+ @ApiModelProperty("上报时间")
+ private String timeStamp;
+
+ @ApiModelProperty("数据类型")
+ private String dataType;
+
+ @ApiModelProperty("目标所属车道")
+ private String targetLane;
+
+ @ApiModelProperty("车辆类型")
+ private String carType;
+
+ @ApiModelProperty("目标速度")
+ private String targetSpeed;
+
+ @ApiModelProperty("目标经度")
+ private String targetLongitude;
+
+ @ApiModelProperty("目标纬度")
+ private String targetLatitude;
+
+ @ApiModelProperty("实时车流量")
+ private String realTimeTrafficFlow;
+
+ @ApiModelProperty("平均车速")
+ private String averageCarSpeed;
+
+ @ApiModelProperty("最大排队长度")
+ private String maxQueueLength;
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarObjectDataVO.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarObjectDataVO.java
new file mode 100644
index 0000000..141c1a1
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarObjectDataVO.java
@@ -0,0 +1,38 @@
+package com.ningdatech.carapi.radar.model.vo;
+
+import java.time.LocalDateTime;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author CMM
+ * @since 2024/10/23 10:49
+ */
+@Data
+@ApiModel(description = "道路监控驾驶舱雷达流量数据-VO")
+@NoArgsConstructor
+@AllArgsConstructor
+public class RadarObjectDataVO {
+
+ @ApiModelProperty("目标所属车道")
+ private String targetLane;
+
+ @ApiModelProperty("车辆类型")
+ private String carType;
+
+ @ApiModelProperty("目标速度")
+ private String targetSpeed;
+
+ @ApiModelProperty("目标经度")
+ private String targetLongitude;
+
+ @ApiModelProperty("目标纬度")
+ private String targetLatitude;
+
+ @ApiModelProperty("上报时间")
+ private LocalDateTime updateOn;
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarTrafficFlowDataVO.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarTrafficFlowDataVO.java
new file mode 100644
index 0000000..c46abac
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/model/vo/RadarTrafficFlowDataVO.java
@@ -0,0 +1,32 @@
+package com.ningdatech.carapi.radar.model.vo;
+
+import java.time.LocalDateTime;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author CMM
+ * @since 2024/10/23 10:49
+ */
+@Data
+@ApiModel(description = "道路监控驾驶舱雷达流量数据-VO")
+@NoArgsConstructor
+@AllArgsConstructor
+public class RadarTrafficFlowDataVO {
+
+ @ApiModelProperty("实时车流量")
+ private String realTimeTrafficFlow;
+
+ @ApiModelProperty("平均车速")
+ private String averageCarSpeed;
+
+ @ApiModelProperty("最大排队长度")
+ private String maxQueueLength;
+
+ @ApiModelProperty("上报时间")
+ private LocalDateTime updateOn;
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/properties/RadarDataTaskProperties.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/properties/RadarDataTaskProperties.java
index fb81035..d8c46dd 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/properties/RadarDataTaskProperties.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/properties/RadarDataTaskProperties.java
@@ -21,31 +21,19 @@ public class RadarDataTaskProperties {
*/
private Boolean enable;
- // 雷达设备的IP地址
- // 黄山隧道义乌来向
- private String hsYwComeRadarIp;
- // 黄山隧道义乌去向
- private String hsYwGoRadarIp;
- // 黄山隧道东阳来向
- private String hsDyComeRadarIp;
- // 黄山隧道东阳去向
- private String hsDyGoRadarIp;
- // 何里隧道义乌方向
- private String hlYwRadarIp;
- // 何里隧道兰溪方向
- private String hlLxRadarIp;
+ // 雷达盒子设备的IP地址
+ // 黄山隧道-义乌方向
+ private String hsYwRadarArmIp;
+ // 黄山隧道-东阳方向
+ private String hsDyRadarArmIp;
+ // 何里隧道
+ private String hlRadarArmIp;
- // 雷达设备监听的端口号
- // 黄山隧道义乌来向
- private Integer hsYwComeRadarPort;
- // 黄山隧道义乌去向
- private Integer hsYwGoRadarPort;
- // 黄山隧道东阳来向
- private Integer hsDyComeRadarPort;
- // 黄山隧道东阳去向
- private Integer hsDyGoRadarPort;
- // 何里隧道义乌方向
- private Integer hlYwRadarPort;
- // 何里隧道兰溪方向
- private Integer hlLxRadarPort;
+ // 雷达盒子监听的端口号
+ // 黄山隧道-义乌方向
+ private Integer hsYwRadarArmPort;
+ // 黄山隧道-东阳方向
+ private Integer hsDyRadarArmPort;
+ // 何里隧道
+ private Integer hlRadarArmPort;
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/server/MultiRadarServer.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/server/MultiRadarServer.java
index d6287e4..990e365 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/server/MultiRadarServer.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/server/MultiRadarServer.java
@@ -1,18 +1,20 @@
package com.ningdatech.carapi.radar.server;
-import com.ningdatech.carapi.radar.client.RadarHandler;
-import com.ningdatech.carapi.radar.model.dto.RadarInfoDTO;
-import com.ningdatech.carapi.radar.service.IRadarDataService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
import java.io.IOException;
-import java.net.*;
+import java.net.Socket;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import com.ningdatech.carapi.radar.client.RadarHandler;
+import com.ningdatech.carapi.radar.helper.RadarDataHelper;
+import com.ningdatech.carapi.radar.model.dto.RadarInfoDTO;
+import com.ningdatech.carapi.radar.service.IRadarDataService;
+
+import cn.hutool.socket.SocketUtil;
+import lombok.extern.slf4j.Slf4j;
+
/**
* @author CMM
* @author CMM
@@ -26,35 +28,32 @@ public class MultiRadarServer {
private final IRadarDataService radarDataService;
private final ExecutorService executorService;
+ private final RadarDataHelper radarDataHelper;
- public MultiRadarServer(IRadarDataService radarDataService,List radarInfoList) {
+ public MultiRadarServer(IRadarDataService radarDataService, List radarInfoList, RadarDataHelper radarDataHelper) {
this.radarDataService = radarDataService;
this.executorService = Executors.newFixedThreadPool(radarInfoList.size());
+ this.radarDataHelper = radarDataHelper;
}
-
public void startServers(List radarInfoList) {
log.info("Radar server started. Waiting for connections...");
for (RadarInfoDTO radarInfo : radarInfoList) {
if (Objects.nonNull(radarInfo.getRadarIp()) && Objects.nonNull(radarInfo.getRadarPort())) {
- // 每个设备创建一个线程 建立一个ServerSocket
+ // 每个设备创建一个线程 建立一个Socket
executorService.execute(() -> {
- try (ServerSocket serverSocket = new ServerSocket(radarInfo.getRadarPort(), 600, InetAddress.getByName(radarInfo.getRadarIp()))){
- // 设置为0表示无限等待,可以根据需要设置超时时间
- serverSocket.setSoTimeout(0);
+ try (Socket socket = SocketUtil.connect(radarInfo.getRadarIp(), radarInfo.getRadarPort(),60000)){
log.info("ServerSocket started on port: {}", radarInfo.getRadarPort());
while (!Thread.currentThread().isInterrupted()) {
- Socket clientSocket = serverSocket.accept();
- //log.info("Connected to a radar on port: {}", radarInfo.getRadarPort());
- RadarHandler task = new RadarHandler(clientSocket, radarDataService, radarInfo.getRadarIp());
+ log.info("Connected to a radar on port: {}", radarInfo.getRadarPort());
+ RadarHandler task = new RadarHandler(socket, radarDataService, radarInfo.getRadarIp(),radarDataHelper);
Thread thread = new Thread(task);
thread.start();
}
} catch (IOException e) {
- log.error("Error occurred while accepting client connection: ", e);
+ log.error("Error occurred while client connection: ", e);
}
});
-
}
}
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/service/IRadarOriginalDataService.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/service/IRadarOriginalDataService.java
new file mode 100644
index 0000000..aaf1a19
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/service/IRadarOriginalDataService.java
@@ -0,0 +1,16 @@
+package com.ningdatech.carapi.radar.service;
+
+import com.ningdatech.carapi.radar.model.entity.RadarOriginalData;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ *
+ * 服务类
+ *
+ *
+ * @author CMM
+ * @since 2024-11-13
+ */
+public interface IRadarOriginalDataService extends IService {
+
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/service/impl/RadarOriginalDataServiceImpl.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/service/impl/RadarOriginalDataServiceImpl.java
new file mode 100644
index 0000000..d6d6cdf
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/service/impl/RadarOriginalDataServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ningdatech.carapi.radar.service.impl;
+
+import com.ningdatech.carapi.radar.model.entity.RadarOriginalData;
+import com.ningdatech.carapi.radar.mapper.RadarOriginalDataMapper;
+import com.ningdatech.carapi.radar.service.IRadarOriginalDataService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ * 服务实现类
+ *
+ *
+ * @author CMM
+ * @since 2024-11-13
+ */
+@Service
+public class RadarOriginalDataServiceImpl extends ServiceImpl implements IRadarOriginalDataService {
+
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/task/RadarDataTask.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/task/RadarDataTask.java
index a7b2682..bd361ee 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/task/RadarDataTask.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/task/RadarDataTask.java
@@ -2,11 +2,10 @@ package com.ningdatech.carapi.radar.task;
import java.util.List;
-import javax.annotation.PostConstruct;
-
import org.apache.commons.compress.utils.Lists;
import org.springframework.stereotype.Component;
+import com.ningdatech.carapi.radar.helper.RadarDataHelper;
import com.ningdatech.carapi.radar.model.dto.RadarInfoDTO;
import com.ningdatech.carapi.radar.properties.RadarDataTaskProperties;
import com.ningdatech.carapi.radar.server.MultiRadarServer;
@@ -27,15 +26,16 @@ public class RadarDataTask {
private final RadarDataTaskProperties properties;
private final IRadarDataService radarDataService;
+ private final RadarDataHelper radarDataHelper;
- @PostConstruct
- public void initTask() {
- if (!properties.getEnable()) {
- log.warn("雷达数据同步已关闭……");
- return;
- }
- //initRadarDataTaskByStart();
- }
+ //@PostConstruct
+ //public void initTask() {
+ // if (!properties.getEnable()) {
+ // log.warn("雷达数据同步已关闭……");
+ // return;
+ // }
+ // //initRadarDataTaskByStart();
+ //}
/**
* 项目重启之后重新初始化雷达接收定时任务
@@ -43,13 +43,11 @@ public class RadarDataTask {
private void initRadarDataTaskByStart() {
log.info("雷达数据接收任务已启动……");
List radarInfoList = Lists.newArrayList();
- radarInfoList.add(new RadarInfoDTO(properties.getHsYwComeRadarIp(),properties.getHsYwComeRadarPort()));
- radarInfoList.add(new RadarInfoDTO(properties.getHsYwGoRadarIp(),properties.getHsYwGoRadarPort()));
- radarInfoList.add(new RadarInfoDTO(properties.getHsDyComeRadarIp(),properties.getHsDyComeRadarPort()));
- radarInfoList.add(new RadarInfoDTO(properties.getHsDyGoRadarIp(),properties.getHsDyGoRadarPort()));
- radarInfoList.add(new RadarInfoDTO(properties.getHlYwRadarIp(),properties.getHlYwRadarPort()));
- radarInfoList.add(new RadarInfoDTO(properties.getHlLxRadarIp(),properties.getHlLxRadarPort()));
- MultiRadarServer server = new MultiRadarServer(radarDataService,radarInfoList);
+ // 每个盒子开启一个线程
+ radarInfoList.add(new RadarInfoDTO(properties.getHsYwRadarArmIp(),properties.getHsYwRadarArmPort()));
+ radarInfoList.add(new RadarInfoDTO(properties.getHsDyRadarArmIp(),properties.getHsDyRadarArmPort()));
+ radarInfoList.add(new RadarInfoDTO(properties.getHlRadarArmIp(),properties.getHlRadarArmPort()));
+ MultiRadarServer server = new MultiRadarServer(radarDataService,radarInfoList,radarDataHelper);
server.startServers(radarInfoList);
}
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/task/RadarRealTimeDataUpdateTask.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/task/RadarRealTimeDataUpdateTask.java
new file mode 100644
index 0000000..45efbfd
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/radar/task/RadarRealTimeDataUpdateTask.java
@@ -0,0 +1,240 @@
+package com.ningdatech.carapi.radar.task;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import cn.hutool.core.collection.CollUtil;
+import com.google.common.collect.Lists;
+import com.ningdatech.carapi.radar.helper.RadarDataHelper;
+import com.ningdatech.carapi.radar.model.entity.RadarData;
+import com.ningdatech.carapi.radar.model.entity.RadarOriginalData;
+import com.ningdatech.carapi.radar.model.vo.RadarDataVO;
+import com.ningdatech.carapi.radar.service.IRadarDataService;
+import com.ningdatech.carapi.radar.service.IRadarOriginalDataService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ningdatech.carapi.gps.manage.GpsDataPullManage;
+
+import cn.hutool.core.date.StopWatch;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author CMM
+ * 雷达实时数据更新定时任务
+ * @since 2024/10/12 10:39
+ */
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class RadarRealTimeDataUpdateTask {
+
+ @Value("${task.switch.is-open}")
+ private boolean flag;
+
+ @Value("${task.gps-data-pull.domain}")
+ private String domain;
+
+ @Value("${task.gps-data-pull.real-time-data-url}")
+ private String realTimeDataUrl;
+
+ @Value("${task.gps-data-pull.key}")
+ private String key;
+
+ private final GpsDataPullManage gpsDataPullManage;
+ private final IRadarOriginalDataService radarOriginalDataService;
+ private final RadarDataHelper radarDataHelper;
+ private final IRadarDataService radarDataService;
+
+ @Value("${spring.datasource.url}")
+ private String dataBaseUrl;
+ @Value("${spring.datasource.username}")
+ private String username;
+ @Value("${spring.datasource.password}")
+ private String password;
+
+ // 定时更新雷达实时数据 每1分钟一次
+ @Scheduled(cron = "0 */1 * * * ?")
+ public void doTask() {
+ if (!flag){
+ log.info("雷达实时数据更新定时任务未开启!");
+ return;
+ }
+ log.info("=========== 雷达实时数据更新 ======== 任务开始");
+ StopWatch stopWatch = new StopWatch();
+ stopWatch.start();
+ // 从表中分别获取三个盒子的最新的目标轨迹数据和交通流量数据
+ List dataList = radarOriginalDataService.list();
+ log.info("=========== 雷达实时数据更新 ======== 获取数据条数:{}",dataList.size());
+ // 按照盒子ip分组
+ Map> dataMap = dataList.stream().collect(Collectors.groupingBy(RadarOriginalData::getArmIp));
+ List finalDataList = Lists.newArrayList();
+ for (Map.Entry> entry : dataMap.entrySet()) {
+ RadarOriginalData data = new RadarOriginalData();
+ // 针对每个盒子分别解析数据
+ String armIp = entry.getKey();
+ List datas = entry.getValue();
+ log.info("=========== 雷达实时数据更新 ======== 盒子ip:{},数据条数:{}",armIp, datas.size());
+ // 盒子有对应的原始数据
+ if (CollUtil.isNotEmpty(datas)) {
+ // 根据数据类型进行分组
+ Map> dataTypeMap = datas.stream().filter(d -> Objects.nonNull(d.getDataType())).collect(Collectors.groupingBy(RadarOriginalData::getDataType));
+ // 获取每个数据类型(目标轨迹数据或者交通流量数据)的数据中最新的一条数据
+ for (Map.Entry> dataTypeEntry : dataTypeMap.entrySet()) {
+ String dataType = dataTypeEntry.getKey();
+ List dataTypeDatas = dataTypeEntry.getValue();
+ if (Objects.nonNull(dataTypeDatas) && CollUtil.isNotEmpty(dataTypeDatas)) {
+ log.info("=========== 雷达实时数据更新 ======== 数据类型:{},数据量:{}",dataType,dataTypeDatas.size());
+ // 按照创建时间排序 取最新的一条数据
+ RadarOriginalData radarOriginalData1 = dataTypeDatas.get(0);
+ log.info("=========== 雷达实时数据更新 ======== 数据类型:{},最新数据:{}",dataType,radarOriginalData1);
+ // 按照创建时间排序 取最新的一条数据
+ RadarOriginalData radarOriginalData = dataTypeDatas.stream().filter(d -> Objects.nonNull(d) && Objects.nonNull(d.getCreateOn())).max(Comparator.comparing(RadarOriginalData::getCreateOn)).orElse(null);
+ if (Objects.nonNull(radarOriginalData)) {
+ data.setId(radarOriginalData.getId());
+ data.setData(radarOriginalData.getData());
+ data.setArmIp(armIp);
+ data.setDataType(dataType);
+ finalDataList.add(data);
+ }
+ }
+ }
+ }
+ }
+ List finalIdList = finalDataList.stream().map(RadarOriginalData::getId).collect(Collectors.toList());
+ // 过滤出dataList中其他数据 从表中删除
+ if (CollUtil.isNotEmpty(finalIdList)) {
+ List removeIdList = dataList.stream().map(RadarOriginalData::getId).filter(id -> !finalIdList.contains(id)).collect(Collectors.toList());
+ radarOriginalDataService.removeBatchByIds(removeIdList);
+ log.info("=========== 雷达实时数据更新 ======== 删除数据条数:{}", removeIdList.size());
+ }
+ // 处理数据
+ List objects = Lists.newArrayList();
+ for (RadarOriginalData radarOriginalData : finalDataList) {
+ String armIp = radarOriginalData.getArmIp();
+ String dataType = radarOriginalData.getDataType();
+ String data = radarOriginalData.getData();
+ List radarDataList = radarDataHelper.getRadarDataList(data);
+ if (CollUtil.isNotEmpty(radarDataList)) {
+ // 获取radarDataList中数据类型为dataType的数据
+ List dataTypeDataList = radarDataList.stream().filter(radarDataVO -> radarDataVO.getDataType().equals(dataType)).collect(Collectors.toList());
+ if (CollUtil.isNotEmpty(dataTypeDataList)) {
+ for (RadarDataVO radarDataVO : dataTypeDataList) {
+ RadarData radarData = new RadarData();
+ radarData.setArmIp(armIp);
+ Map deviceIdRadarIpMap = new HashMap<>();
+ deviceIdRadarIpMap.put("21626092", "192.168.6.43");
+ deviceIdRadarIpMap.put("21626098", "192.168.6.40");
+ deviceIdRadarIpMap.put("21626114", "192.168.6.45");
+ deviceIdRadarIpMap.put("21626163", "192.168.6.47");
+ deviceIdRadarIpMap.put("21626197", "192.168.10.63");
+ deviceIdRadarIpMap.put("21626107", "192.168.10.60");
+ String radarIp = deviceIdRadarIpMap.getOrDefault(radarDataVO.getDeviceId(), null);
+ radarData.setRadarIp(radarIp);
+ String voDataType = radarDataVO.getDataType();
+ if ("1".equals(voDataType)) {
+ radarData.setDataType(1);
+ } else if ("3".equals(voDataType)) {
+ radarData.setDataType(2);
+ }
+ radarData.setCreateOn(LocalDateTime.now());
+ radarData.setUpdateOn(LocalDateTime.now());
+
+ radarData.setTargetLane(radarDataVO.getTargetLane());
+
+ Map carTypeMap = new HashMap<>();
+ carTypeMap.put("00", 4);
+ carTypeMap.put("01", 1);
+ carTypeMap.put("02", 2);
+ carTypeMap.put("03", 3);
+
+ Integer type = carTypeMap.getOrDefault(radarDataVO.getCarType(), 4);
+ radarData.setCarType(type);
+ radarData.setTargetSpeed(radarDataVO.getTargetSpeed());
+ radarData.setTargetLongitude(radarDataVO.getTargetLongitude());
+ radarData.setTargetLatitude(radarDataVO.getTargetLatitude());
+
+ radarData.setRealTimeTrafficFlow(radarDataVO.getRealTimeTrafficFlow());
+ radarData.setAverageCarSpeed(radarDataVO.getAverageCarSpeed());
+ radarData.setMaxQueueLength(radarDataVO.getMaxQueueLength());
+ radarData.setCreateOn(LocalDateTime.now());
+ radarData.setUpdateOn(LocalDateTime.now());
+ objects.add(radarData);
+ }
+ }
+ }
+ }
+ // 插入数据
+ if (CollUtil.isNotEmpty(objects)) {
+ radarDataService.saveBatch(objects);
+ log.info("=========== 雷达实时数据更新 ======== 插入数据条数:{}",objects.size());
+ }
+ stopWatch.stop();
+ log.info("=========== 雷达实时数据更新 ======== 任务结束 {}s",stopWatch.getTotalTimeSeconds());
+ }
+
+ private void truncateTable() {
+ // SQL 语句清空表
+ String truncateTableSQL = "TRUNCATE TABLE nd_data_access_gps_real_time_data";
+
+ Connection conn = null;
+ Statement stmt = null;
+
+ try {
+ // 加载数据库驱动
+ Class.forName("com.mysql.cj.jdbc.Driver");
+ // 建立连接
+ conn = DriverManager.getConnection(dataBaseUrl, username, password);
+ // 创建 Statement 对象
+ stmt = conn.createStatement();
+ // 执行 SQL 语句
+ stmt.executeUpdate(truncateTableSQL);
+ log.info("表已被清空,自增主键已重置。");
+ } catch (Exception e) {
+ log.error("Error truncating table: {}", e.getMessage());
+ } finally {
+ // 关闭资源
+ try {
+ if (stmt != null) {
+ stmt.close();
+ }
+ if (conn != null) {
+ conn.close();
+ }
+ } catch (Exception e) {
+ log.error("Error closing resources: {}", e.getMessage());
+ }
+ }
+ }
+
+ private Map assemblyParams(String key, String sLon, String eLon, String sLat, String eLat) {
+ long timeNow = System.currentTimeMillis() / 1000;
+ String token = gpsDataPullManage.generateMD5Token(timeNow, key);
+
+ Map params = new HashMap<>();
+ params.put("timeNow", String.valueOf(timeNow));
+ params.put("token", token);
+ if (StringUtils.isNotBlank(sLon)) {
+ params.put("Slon", sLon);
+ }
+ if (StringUtils.isNotBlank(eLon)) {
+ params.put("Elon", eLon);
+ }
+ if (StringUtils.isNotBlank(sLat)) {
+ params.put("Slat", sLat);
+ }
+ if (StringUtils.isNotBlank(eLat)) {
+ params.put("Elat", eLat);
+ }
+ return params;
+ }
+
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/controller/RoadMonitorController.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/controller/RoadMonitorController.java
index a80f855..a95c6f0 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/controller/RoadMonitorController.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/controller/RoadMonitorController.java
@@ -1,21 +1,23 @@
package com.ningdatech.carapi.road.controller;
import com.ningdatech.carapi.road.manage.RoadMonitorManage;
+import com.ningdatech.carapi.road.model.req.RoadMonitorHandleReq;
import com.ningdatech.carapi.road.model.req.RoadMonitorReq;
import com.ningdatech.carapi.road.model.req.VideoDownloadReq;
import com.ningdatech.carapi.road.model.vo.ComprehensiveSituationVO;
+import com.ningdatech.carapi.radar.model.vo.RadarTrafficFlowDataVO;
import com.ningdatech.carapi.road.model.vo.RoadDangerBehaviorVO;
+import com.ningdatech.carapi.road.model.vo.VehicleGpsDataVO;
import com.ningdatech.log.annotation.WebLog;
import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
import java.util.List;
/**
@@ -53,10 +55,30 @@ public class RoadMonitorController {
return roadMonitorManage.getViolationWarnRecordData(req);
}
+ @ApiOperation(value = "危险行为数据处理", notes = "危险行为数据处理")
+ @PostMapping("/handle-violation-record")
+ public String handleViolationRecord(@Valid @RequestBody RoadMonitorHandleReq req) {
+ return roadMonitorManage.handleViolationRecord(req);
+ }
+
+ @ApiOperation(value = "数据驾驶舱GPS数据", notes = "数据驾驶舱GPS数据")
+ @GetMapping("/vehicle-gps-data")
+ public List getVehicleGpsData(RoadMonitorReq req) {
+ return roadMonitorManage.getVehicleGpsData(req);
+ }
+
+
@GetMapping("/video/download")
@ApiOperation("视频下载")
@WebLog("视频下载")
- public void downloadOperationManual(VideoDownloadReq req, HttpServletResponse response){
+ public void videoDownload(VideoDownloadReq req, HttpServletResponse response){
roadMonitorManage.videoDownload(req,response);
}
+
+ @GetMapping("/picture/download")
+ @ApiOperation("图片下载")
+ @WebLog("图片下载")
+ public void pictureDownload(VideoDownloadReq req, HttpServletResponse response){
+ roadMonitorManage.pictureDownload(req,response);
+ }
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/manage/RoadMonitorManage.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/manage/RoadMonitorManage.java
index ba1e5c1..e6fb689 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/manage/RoadMonitorManage.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/manage/RoadMonitorManage.java
@@ -3,15 +3,18 @@ package com.ningdatech.carapi.road.manage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
+import com.ningdatech.carapi.radar.helper.RadarDataHelper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
@@ -20,12 +23,16 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.carapi.common.contants.DefValConstants;
+import com.ningdatech.carapi.homepage.entity.model.NdDataAccessGps;
+import com.ningdatech.carapi.homepage.service.IDataAccessGpsService;
import com.ningdatech.carapi.road.constant.*;
import com.ningdatech.carapi.road.model.entity.RoadBehaviorAnalysis;
+import com.ningdatech.carapi.road.model.req.RoadMonitorHandleReq;
import com.ningdatech.carapi.road.model.req.RoadMonitorReq;
import com.ningdatech.carapi.road.model.req.VideoDownloadReq;
import com.ningdatech.carapi.road.model.vo.ComprehensiveSituationVO;
import com.ningdatech.carapi.road.model.vo.RoadDangerBehaviorVO;
+import com.ningdatech.carapi.road.model.vo.VehicleGpsDataVO;
import com.ningdatech.carapi.road.service.IRoadBehaviorAnalysisService;
import com.ningdatech.carapi.scheduler.contants.TaskConstant;
@@ -53,11 +60,13 @@ public class RoadMonitorManage {
private String redisPassword;
private final IRoadBehaviorAnalysisService roadBehaviorAnalysisService;
+ private final IDataAccessGpsService dataAccessGpsService;
+ private final RadarDataHelper radarDataHelper;
public ComprehensiveSituationVO getComSitData(RoadMonitorReq req) {
Jedis jedis = new Jedis(redisHost,redisPort);
jedis.auth(redisPassword);
- // 隧道类型
+ // 隧道类型 1 黄山隧道、2 何里隧道、3 两条隧道的汇总(求平均)
Integer tunnel = req.getTunnel();
// 根据传入的隧道类型 实时从缓存中获取算法分析的结果
// 分区域分别计算4个指标的分值 比例按照1:1:1:1 加权计算道路安全指数
@@ -70,6 +79,9 @@ public class RoadMonitorManage {
else if (tunnel == 2) {
List regionList = Lists.newArrayList("5", "6");
return getCollectDataVo(regionList,jedis,tunnel);
+ } else if (tunnel == 3) {
+ List regionList = Lists.newArrayList("1", "2", "3", "4", "5", "6");
+ return getCollectDataVo(regionList, jedis, tunnel);
}
return new ComprehensiveSituationVO();
}
@@ -133,6 +145,9 @@ public class RoadMonitorManage {
} else if (tunnel == 2) {
List vos = Lists.newArrayList(hlLxVo, hlYwVo);
vo = averageScores(vos);
+ } else if (tunnel == 3) {
+ List vos = Lists.newArrayList(hsDyGoVo, hsDyComeVo, hsYwGoVo, hsYwComeVo, hlLxVo, hlYwVo);
+ vo = averageScores(vos);
}
return vo;
}
@@ -375,21 +390,25 @@ public class RoadMonitorManage {
// 从列表中查询数据
List list = roadBehaviorAnalysisService.list(Wrappers.lambdaQuery(RoadBehaviorAnalysis.class)
.in(RoadBehaviorAnalysis::getRegion, regionList)
+ .like(StringUtils.isNoneBlank(req.getBehavior()),RoadBehaviorAnalysis::getBehavior, req.getBehavior())
+ .eq(Objects.nonNull(req.getType()),RoadBehaviorAnalysis::getType, req.getType())
+ .ge(Objects.nonNull(req.getStartTime()), RoadBehaviorAnalysis::getBehaviorTime, req.getStartTime())
+ .le(Objects.nonNull(req.getEndTime()), RoadBehaviorAnalysis::getBehaviorTime, req.getEndTime())
+ .eq(Objects.nonNull(req.getIsWarn()),RoadBehaviorAnalysis::getIsWarn, req.getIsWarn())
.orderByDesc(RoadBehaviorAnalysis::getBehaviorTime));
if (CollUtil.isEmpty(list)){
return Collections.emptyList();
}
return list.stream().map(roadBehaviorAnalysis -> {
RoadDangerBehaviorVO vo = new RoadDangerBehaviorVO();
+ vo.setId(roadBehaviorAnalysis.getId());
vo.setRecord(roadBehaviorAnalysis.getBehavior());
vo.setRecordCode(roadBehaviorAnalysis.getBehaviorCode());
vo.setRecordTime(roadBehaviorAnalysis.getBehaviorTime());
vo.setRecordType(RoadBehaviorTypeEnum.getDescByCode(roadBehaviorAnalysis.getType()));
vo.setRegion(roadBehaviorAnalysis.getRegion());
vo.setPicturePath(roadBehaviorAnalysis.getPicturePath());
- vo.setVideoPath(roadBehaviorAnalysis.getVideoPath());
- vo.setLatitude(roadBehaviorAnalysis.getLatitude());
- vo.setLongitude(roadBehaviorAnalysis.getLongitude());
+ vo.setOverspeedPositionInfo(roadBehaviorAnalysis.getOverspeedPositionInfo());
vo.setIsWarn(roadBehaviorAnalysis.getIsWarn());
vo.setIsHandled(roadBehaviorAnalysis.getIsHandled());
return vo;
@@ -464,18 +483,100 @@ public class RoadMonitorManage {
}
return list.stream().map(roadBehaviorAnalysis -> {
RoadDangerBehaviorVO vo = new RoadDangerBehaviorVO();
+ vo.setId(roadBehaviorAnalysis.getId());
vo.setRecord(roadBehaviorAnalysis.getBehavior());
vo.setRecordCode(roadBehaviorAnalysis.getBehaviorCode());
vo.setRecordTime(roadBehaviorAnalysis.getBehaviorTime());
vo.setRecordType(RoadBehaviorTypeEnum.getDescByCode(roadBehaviorAnalysis.getType()));
vo.setRegion(roadBehaviorAnalysis.getRegion());
vo.setPicturePath(roadBehaviorAnalysis.getPicturePath());
- vo.setVideoPath(roadBehaviorAnalysis.getVideoPath());
- vo.setLatitude(roadBehaviorAnalysis.getLatitude());
- vo.setLongitude(roadBehaviorAnalysis.getLongitude());
+ vo.setOverspeedPositionInfo(roadBehaviorAnalysis.getOverspeedPositionInfo());
vo.setIsWarn(roadBehaviorAnalysis.getIsWarn());
vo.setIsHandled(roadBehaviorAnalysis.getIsHandled());
return vo;
}).collect(Collectors.toList());
}
+
+ @Transactional(rollbackFor = Exception.class)
+ public String handleViolationRecord(RoadMonitorHandleReq req) {
+ Long id = req.getId();
+ // 获取危险行为数据
+ RoadBehaviorAnalysis roadBehaviorAnalysis = roadBehaviorAnalysisService.getById(id);
+ if (Objects.isNull(roadBehaviorAnalysis)){
+ throw new BizException("数据不存在!");
+ }
+ if (Boolean.TRUE.equals(roadBehaviorAnalysis.getIsHandled())){
+ throw new BizException("数据已处理!");
+ }
+ // 更新对应数据状态为已处理
+ roadBehaviorAnalysis.setIsHandled(Boolean.TRUE);
+ if (roadBehaviorAnalysisService.updateById(roadBehaviorAnalysis)) {
+ return "处理成功!";
+ }else {
+ throw new BizException("处理失败!");
+ }
+ }
+
+ public List getVehicleGpsData(RoadMonitorReq req) {
+ // TODO 暂时不确定隧道的经纬度范围 确定后 需要根据查询的隧道 筛选出对应经纬度范围内的GPS数据 目前返回全部的
+ // 只查询最新的5分钟内的数据
+ LocalDateTime startTime = LocalDateTime.now().minusMinutes(5);
+ LocalDateTime endTime = LocalDateTime.now();
+ List data = dataAccessGpsService.list(Wrappers.lambdaQuery(NdDataAccessGps.class)
+ .ge(NdDataAccessGps::getUpdateTime, startTime).le(NdDataAccessGps::getUpdateTime, endTime)
+ .select(NdDataAccessGps::getCarLongitude, NdDataAccessGps::getCarLatitude, NdDataAccessGps::getCarPlate,
+ NdDataAccessGps::getCarVelocity, NdDataAccessGps::getCarDirection, NdDataAccessGps::getUpdateTime));
+ log.info("最近5分钟 查询到{}条数据", data.size());
+ // 如果最近15分钟没有数据(GPS数据不更新了) 取表中最新的5000条数据
+ if (CollUtil.isEmpty(data)){
+ data = dataAccessGpsService.list(Wrappers.lambdaQuery(NdDataAccessGps.class)
+ .select(NdDataAccessGps::getCarLongitude, NdDataAccessGps::getCarLatitude,
+ NdDataAccessGps::getCarPlate,NdDataAccessGps::getCarVelocity,
+ NdDataAccessGps::getCarDirection, NdDataAccessGps::getUpdateTime)
+ .orderByDesc(NdDataAccessGps::getUpdateTime).last("limit 5000"));
+ }
+ return data.stream().map(d -> {
+ VehicleGpsDataVO vo = new VehicleGpsDataVO();
+ vo.setCarPlate(d.getCarPlate());
+ vo.setCarVelocity(d.getCarVelocity());
+ vo.setCarDirection(d.getCarDirection());
+ vo.setLongitude(d.getCarLongitude());
+ vo.setLatitude(d.getCarLatitude());
+ vo.setUpdateTime(d.getUpdateTime());
+ return vo;
+ }).collect(Collectors.toList());
+ }
+
+ public void pictureDownload(VideoDownloadReq req, HttpServletResponse response) {
+
+ String picturePath = req.getPicturePath();
+ // 将路径中的文件分割符替换为系统能够识别的分隔符
+ picturePath = picturePath.replace("\\", File.separator);
+ picturePath = picturePath.replace("/", File.separator);
+
+ File file = new File(picturePath);
+
+ // 设置文件名(将显示在下载对话框中)
+ response.setHeader("Content-Disposition", "inline;");
+ // 设置文件大小,这样浏览器可以显示下载进度
+ response.setContentLengthLong(file.length());
+
+ // 设置缓存控制头
+ response.setHeader("Cache-Control", "public, max-age=31536000");
+
+ try {
+ FileInputStream fileInputStream = new FileInputStream(file);
+ ServletOutputStream servletOutputStream = response.getOutputStream();
+
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = fileInputStream.read(buffer)) != -1) {
+ servletOutputStream.write(buffer, 0, bytesRead);
+ }
+ fileInputStream.close();
+ servletOutputStream.close();
+ } catch (IOException e) {
+ throw new BizException("文件下载失败! {}",e);
+ }
+ }
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/OverspeedPositionInfo.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/OverspeedPositionInfo.java
new file mode 100644
index 0000000..d9f1989
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/OverspeedPositionInfo.java
@@ -0,0 +1,17 @@
+package com.ningdatech.carapi.road.model;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @author CMM
+ * @since 2024/11/08 10:55
+ */
+@Data
+public class OverspeedPositionInfo {
+
+ @ApiModelProperty("经度")
+ private String longitude;
+ @ApiModelProperty("纬度")
+ private String latitude;
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/entity/RoadBehaviorAnalysis.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/entity/RoadBehaviorAnalysis.java
index 5636074..9306d1e 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/entity/RoadBehaviorAnalysis.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/entity/RoadBehaviorAnalysis.java
@@ -46,14 +46,8 @@ public class RoadBehaviorAnalysis implements Serializable {
@ApiModelProperty("异常记录图片路径")
private String picturePath;
- @ApiModelProperty("异常记录视频路径")
- private String videoPath;
-
- @ApiModelProperty("异常记录经度")
- private String longitude;
-
- @ApiModelProperty("异常记录纬度")
- private String latitude;
+ @ApiModelProperty("超速异常记录位置信息")
+ private String overspeedPositionInfo;
@ApiModelProperty("是否需要预警")
private Boolean isWarn = Boolean.FALSE;
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/RoadMonitorHandleReq.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/RoadMonitorHandleReq.java
new file mode 100644
index 0000000..9d3e16b
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/RoadMonitorHandleReq.java
@@ -0,0 +1,26 @@
+package com.ningdatech.carapi.road.model.req;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @author CMM
+ * @author CMM
+ * @date 2024/10/23 17:53
+ * /
+ * /**
+ * @since 2024/10/23 17:53
+ */
+@Data
+public class RoadMonitorHandleReq {
+
+ @ApiModelProperty("记录ID")
+ @NotNull(message = "记录ID不能为空!")
+ private Long id;
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/RoadMonitorReq.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/RoadMonitorReq.java
index 4b449dc..4fbdb8b 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/RoadMonitorReq.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/RoadMonitorReq.java
@@ -1,5 +1,6 @@
package com.ningdatech.carapi.road.model.req;
+import java.time.LocalDateTime;
import java.util.List;
import io.swagger.annotations.ApiModelProperty;
@@ -21,7 +22,7 @@ public class RoadMonitorReq {
@ApiModelProperty("区域 1 黄山隧道东阳去向、2 黄山隧道东阳来向、3 黄山隧道义乌去向、4 黄山隧道义乌来向、5 何里隧道兰溪方向、6 何里隧道义乌方向")
private String region;
- @ApiModelProperty("隧道 1 黄山隧道、2 何里隧道")
+ @ApiModelProperty("隧道 1 黄山隧道、2 何里隧道、3 两条隧道汇总")
private Integer tunnel;
@ApiModelProperty("隧道包含的区域 列表")
@@ -30,4 +31,16 @@ public class RoadMonitorReq {
@ApiModelProperty("行为类型 1 驾驶员、2 车辆、3 道路、4 环境")
private Integer type;
+
+ @ApiModelProperty("危险行为")
+ private String behavior;
+
+ @ApiModelProperty("开始时间")
+ private LocalDateTime startTime;
+
+ @ApiModelProperty("结束时间")
+ private LocalDateTime endTime;
+
+ @ApiModelProperty("是否预警")
+ private Boolean isWarn;
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/VideoDownloadReq.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/VideoDownloadReq.java
index 9acd872..9b463ff 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/VideoDownloadReq.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/req/VideoDownloadReq.java
@@ -21,4 +21,7 @@ public class VideoDownloadReq {
@ApiModelProperty("1 2 3 4 5 6")
private String videoOrder;
+
+ @ApiModelProperty("图片路径")
+ private String picturePath;
}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/vo/RoadDangerBehaviorVO.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/vo/RoadDangerBehaviorVO.java
index 3f9f96a..42d7380 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/vo/RoadDangerBehaviorVO.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/vo/RoadDangerBehaviorVO.java
@@ -18,6 +18,9 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class RoadDangerBehaviorVO {
+ @ApiModelProperty("危险行为ID")
+ private Long id;
+
@ApiModelProperty("道路异常分析记录")
private String record;
@@ -39,11 +42,8 @@ public class RoadDangerBehaviorVO {
@ApiModelProperty("异常记录视频路径")
private String videoPath;
- @ApiModelProperty("异常记录经度")
- private String longitude;
-
- @ApiModelProperty("异常记录纬度")
- private String latitude;
+ @ApiModelProperty("超速异常记录位置信息")
+ private String overspeedPositionInfo;
@ApiModelProperty("是否需要预警")
private Boolean isWarn = Boolean.FALSE;
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/vo/VehicleGpsDataVO.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/vo/VehicleGpsDataVO.java
new file mode 100644
index 0000000..030c408
--- /dev/null
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/model/vo/VehicleGpsDataVO.java
@@ -0,0 +1,39 @@
+package com.ningdatech.carapi.road.model.vo;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author CMM
+ * @since 2024/10/23 10:49
+ */
+@Data
+@ApiModel(description = "数据驾驶舱车辆GPS数据-VO")
+@NoArgsConstructor
+@AllArgsConstructor
+public class VehicleGpsDataVO {
+
+ @ApiModelProperty("车牌号")
+ private String carPlate;
+
+ @ApiModelProperty("车速")
+ private BigDecimal carVelocity;
+
+ @ApiModelProperty("车辆方向")
+ private BigDecimal carDirection;
+
+ @ApiModelProperty("上报时间")
+ private LocalDateTime updateTime;
+
+ @ApiModelProperty("经度")
+ private BigDecimal longitude;
+
+ @ApiModelProperty("纬度")
+ private BigDecimal latitude;
+}
diff --git a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/task/RoadMonitorDataPullTask.java b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/task/RoadMonitorDataPullTask.java
index d191d92..c473f61 100644
--- a/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/task/RoadMonitorDataPullTask.java
+++ b/ningda-yw-api/src/main/java/com/ningdatech/carapi/road/task/RoadMonitorDataPullTask.java
@@ -6,6 +6,7 @@ import java.util.stream.Collectors;
import cn.hutool.core.map.MapUtil;
import com.ningdatech.basic.util.StrPool;
+import com.ningdatech.carapi.road.model.OverspeedPositionInfo;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
@@ -342,26 +343,9 @@ public class RoadMonitorDataPullTask {
String time = jsonObject.getString("Time");
String region = jsonObject.getString("Region");
- JSONArray pictures = jsonObject.getJSONArray("PicturePath");
- String picturePath = null;
- if (Objects.nonNull(pictures) && CollUtil.isNotEmpty(pictures)) {
- picturePath = pictures.getString(0);
- }
- JSONArray videos = jsonObject.getJSONArray("VideoPath");
- String videoPath = null;
- if (Objects.nonNull(videos) && CollUtil.isNotEmpty(videos)) {
- videoPath = videos.getString(0);
- }
+ String picturePath = jsonObject.getString("PicturePath");
JSONArray longitudes = jsonObject.getJSONArray("Longitude");
- String longitude = null;
- if (Objects.nonNull(longitudes) && CollUtil.isNotEmpty(longitudes)){
- longitude = longitudes.getString(0);
- }
JSONArray latitudes = jsonObject.getJSONArray("Latitude");
- String latitude = null;
- if (Objects.nonNull(latitudes) && CollUtil.isNotEmpty(latitudes)){
- latitude = latitudes.getString(0);
- }
// 转换为LocalDateTime类型
LocalDateTime createTime = LocalDateTimeUtil.parse(time, NdDateUtils.DEFAULT_DATE_TIME_FORMAT);
@@ -375,9 +359,19 @@ public class RoadMonitorDataPullTask {
roadBehaviorAnalysis.setRegion(region);
roadBehaviorAnalysis.setType(type);
roadBehaviorAnalysis.setPicturePath(picturePath);
- roadBehaviorAnalysis.setVideoPath(videoPath);
- roadBehaviorAnalysis.setLongitude(longitude);
- roadBehaviorAnalysis.setLatitude(latitude);
+ // 获取超速车辆的经纬度数据
+ if ((Objects.nonNull(longitudes) && CollUtil.isNotEmpty(longitudes)) && (Objects.nonNull(latitudes) && CollUtil.isNotEmpty(latitudes))){
+ List positionInfoList = Lists.newArrayList();
+ // 按一对一的关系 拼接经纬度
+ for (int i = 0; i < longitudes.size(); i++) {
+ OverspeedPositionInfo positionInfo = new OverspeedPositionInfo();
+ positionInfo.setLongitude(longitudes.getString(i));
+ positionInfo.setLatitude(latitudes.getString(i));
+ positionInfoList.add(positionInfo);
+ }
+ // 转化为Json字符串
+ roadBehaviorAnalysis.setOverspeedPositionInfo(JSON.toJSONString(positionInfoList));
+ }
roadBehaviorAnalysis.setIsWarn(judgeIsWarn(behaviorCode));
// 首次保存的数据默认未处理
roadBehaviorAnalysis.setIsHandled(Boolean.FALSE);
diff --git a/ningda-yw-api/src/main/resources/application-dev.yml b/ningda-yw-api/src/main/resources/application-dev.yml
index 309b9b8..ee32fd4 100644
--- a/ningda-yw-api/src/main/resources/application-dev.yml
+++ b/ningda-yw-api/src/main/resources/application-dev.yml
@@ -153,16 +153,10 @@ send-urge-warn:
radar-data-task:
enable: false
# 黄山隧道
- hs-yw-come-radar-ip: 192.168.6.40
- hs-yw-come-radar-port: 10000
- hs-yw-go-radar-ip: 192.168.6.43
- hs-yw-go-radar-port: 20000
- hs-dy-come-radar-ip: 192.168.6.47
- hs-dy-come-radar-port: 30000
- hs-dy-go-radar-ip: 192.168.6.45
- hs-dy-go-radar-port: 40000
+ hs-yw-radar-arm-ip: 192.168.6.42
+ hs-yw-radar-arm-port: 13000
+ hs-dy-radar-arm-ip: 192.168.6.49
+ hs-dy-radar-arm-port: 13001
# 何里隧道
- hl-yw-radar-ip: 192.168.10.60
- hl-yw-radar-port: 50000
- hl-lx-radar-ip: 192.168.10.63
- hl-lx-radar-port: 60000
+ hl-radar-arm-ip: 192.168.10.62
+ hl-radar-arm-port: 13002
diff --git a/ningda-yw-api/src/main/resources/security/auth-dev.yml b/ningda-yw-api/src/main/resources/security/auth-dev.yml
index b779bb9..bf7b6f2 100644
--- a/ningda-yw-api/src/main/resources/security/auth-dev.yml
+++ b/ningda-yw-api/src/main/resources/security/auth-dev.yml
@@ -46,4 +46,5 @@ security:
- /api/car-rpt/**
- /open/api/**
- /radar/**
- - /gps/**
\ No newline at end of file
+ - /gps/**
+ - /road-monitor/**
\ No newline at end of file
diff --git a/ningda-yw-api/src/test/java/com/ningdatech/carapi/radar/RadarTest.java b/ningda-yw-api/src/test/java/com/ningdatech/carapi/radar/RadarTest.java
new file mode 100644
index 0000000..08b8a73
--- /dev/null
+++ b/ningda-yw-api/src/test/java/com/ningdatech/carapi/radar/RadarTest.java
@@ -0,0 +1,437 @@
+package com.ningdatech.carapi.radar;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.google.common.collect.Lists;
+import com.ningdatech.carapi.AppTests;
+import com.ningdatech.carapi.radar.helper.RadarDataHelper;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author CMM
+ * @author CMM
+ * @date 2024/11/8 10:28
+ * /
+ * /**
+ * @since 2024/11/08 10:28
+ */
+@Slf4j
+public class RadarTest extends AppTests {
+
+ @Autowired
+ private RadarDataHelper radarDataHelper;
+
+ @Test
+ public void test(){
+ // 目标轨迹数据
+ //String data = "55aa55bb01d001763231363236303932000000000000000000000000180b0d0f2d0c00530b7e210232140fffffff0e0cfe5c1aa4000a07761adc0000ff4c000047868a4c11e1baee0b4b210232140fffffff0e0cfe167d4600f0097e22560000fe8e000047868a0311e213880b63220232140fffffff003801183f0cffce06c21856000000b4000047868d2211e1dba20b4e210232140fffffff0e0cfe2069aa019007f81d3bffecffa6fef247868a0e11e201eb0b7fff0232140fffffff0debfbaa14faff2407801b2dfff6fe8eff4c4786878011e1b5d70b57210232140fffffff0e0efe3e53e8fff607d01c200000fdda000047868a2d11e1ee5f0b661f0232140fffffff0e0efb32442afff6091a20c40000ffa6ffa64786870411e1e03b0b5d2003961925ffffff0e0cfc9a4a60ffa606cc18800000000000004786887911e1e5cf0b71200232140fffffff0e0ffccc29ae0000073a1a04000000000000478688ad11e1c8710b67200232140fffffff0e0ffcf43c14000007bc1bd7000000000000478688d611e1d8f80b7b200232140fffffff0e0efcb81f900000078a1b230000000000004786889811e1bf5a0b3e210232140fffffff0e0cfdda6cf2007805be14bd0000ff4c0000478689c511e204de55cc55dd";
+ // 交通流量数据
+ String data = "55aa55bb007803703231363236303932000000000000000000000000180b0d102914000f0000001f0064000600000000000602f9001e00660000000000200064000400000000000402e5003200350000000000210064000300000000000302eb004600350000000000220064000200000000000202520064002b000055cc55dd";
+ String head = "55aa55bb";
+ String tail = "55cc55dd";
+
+ // 解析上面的数据帧data 包头是head 包尾是tail 从包头到包尾分段取出数据 放入一个字符串列表中 等待解析
+ List segments = Lists.newArrayList();
+ int headIndex = data.indexOf(head);
+ while (headIndex != -1) {
+ int tailIndex = data.indexOf(tail, headIndex + head.length());
+ if (tailIndex != -1) {
+ // 提取从包头到包尾的数据段
+ String segment = data.substring(headIndex + head.length(), tailIndex);
+ segments.add(segment);
+ // 移动到下一个包头的位置
+ headIndex = data.indexOf(head, tailIndex + tail.length());
+ } else {
+ // 没有找到包尾,结束循环
+ break;
+ }
+ }
+
+ // 遍历这些数据帧 解析其中携带的数据
+ for (String segment : segments) {
+ System.out.println(segment);
+
+ //// 获取数据长度 2字节 4字符 判断数据包长度
+ byte[] dataLengthData = radarDataHelper.extractHexData(segment, 0, 2);
+ String dataLengthHexString = radarDataHelper.byteArrayToHexStringAll(dataLengthData);
+ // 将16进制字符串转换为字节数组
+ byte[] lengthBytes = new byte[2];
+ // 高8位
+ lengthBytes[0] = (byte) Integer.parseInt(dataLengthHexString.substring(0, 2), 16);
+ // 低8位
+ lengthBytes[1] = (byte) Integer.parseInt(dataLengthHexString.substring(2, 4), 16);
+
+ // 将字节数组转换为16位整数,注意网络字节序是大端序
+ int byteLength = ((lengthBytes[0] & 0xFF) << 8) | (lengthBytes[1] & 0xFF);
+ // 打印结果
+ System.out.println("数据长度: " + byteLength);
+
+ // 获取数据帧类型 1字节 2字符 判断是目标轨迹数据还是交通流量数据
+ byte[] objectTypeData = radarDataHelper.extractHexData(segment, 2, 1);
+ String objectTypeHexString = radarDataHelper.byteArrayToHexString(objectTypeData);
+ System.out.println("数据帧类型:" + objectTypeHexString);
+
+ // 获取设备编号 20字节 40字符 判断是哪个雷达的发送的数据
+ byte[] deviceIdData = radarDataHelper.extractHexData(segment, 4, 16);
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < deviceIdData.length; i++) {
+ byte b = deviceIdData[i];
+ // 16进制数 "32"
+ String hexString = String.format("%02X", b);
+ // 将16进制字符串转换为十进制整数
+ int decimalValue = Integer.parseInt(hexString, 16);
+ // 将十进制整数转换为对应的ASCII字符
+ char asciiChar = (char) decimalValue;
+ // 如果不为0则添加到字符串中
+ if (decimalValue != 0) {
+ builder.append(asciiChar);
+ }
+ }
+ System.out.println("设备编号:" + builder);
+
+ //String deviceIdHexString = radarDataHelper.byteArrayToHexString(deviceIdData);
+ //System.out.println("设备编号:" + deviceIdHexString);
+
+ byte[] timestampData = radarDataHelper.extractHexData(segment, 24, 8);
+ String timestampHexString = radarDataHelper.byteArrayToHexString(timestampData);
+ System.out.println("时间戳:" + timestampHexString);
+ String localDateTime = getLocalDateTime(segment, timestampData);
+ System.out.println("时间戳:" + localDateTime);
+ // 获取数据
+ // 用获取到的数据长度 减去数据长度(2字节)、数据帧类型(1字节)、校验和(1字节)、数据编号)、时间戳(8字节)、包头包尾(8字节)
+ int dataLength = byteLength - 2 - 1 - 1 - 20 - 8;
+ segment = segment.substring(64);
+ System.out.println("总数据:" + segment);
+
+ // 如果是目标轨迹数据
+ if (objectTypeHexString.equals("01")) {
+ if (dataLength >= 36) {
+ byte[] dataBytes = radarDataHelper.hexStringToByteArray(segment);
+ // 36个字节为一组数据 进行分组解析
+ for (int i = 0; i < dataBytes.length; i += 36) {
+ byte[] dataSegment = Arrays.copyOfRange(dataBytes, i, i + 36);
+ String dataSegmentHexString = radarDataHelper.byteArrayToHexStringAll(dataSegment);
+ System.out.println("数据:" + dataSegmentHexString);
+ // 获取目标所属车道
+ byte[] targetLaneData = radarDataHelper.extractHexData(dataSegmentHexString, 2, 1);
+ String targetLaneHexString = radarDataHelper.byteArrayToHexStringAll(targetLaneData);
+ // 将16进制数转换为10进制数
+ int targetLane = Integer.parseInt(targetLaneHexString, 16);
+ System.out.println("目标所属车道:" + targetLane);
+ // 获取目标类型
+ byte[] carTypeData = radarDataHelper.extractHexData(dataSegmentHexString, 3, 1);
+ String carTypeHexString = radarDataHelper.byteArrayToHexStringAll(carTypeData);
+ System.out.println("目标类型:" + carTypeHexString);
+ // 获取目标速度
+ byte[] targetSpeedData = radarDataHelper.extractHexData(dataSegmentHexString, 20, 2);
+ String targetSpeedHexString = radarDataHelper.byteArrayToHexStringAll(targetSpeedData);
+ double targetSpeed = Integer.parseInt(targetSpeedHexString, 16) * 0.01;
+ System.out.println("目标速度:" + targetSpeed);
+ // 获取目标经度
+ byte[] targetLongitudeData = radarDataHelper.extractHexData(dataSegmentHexString, 28, 4);
+ String targetLongitudeHexString = radarDataHelper.byteArrayToHexStringAll(targetLongitudeData);
+ // 将十六进制字符串转换为long类型的数字
+ long targetLongitudeLongValue = Long.parseLong(targetLongitudeHexString, 16);
+ // 将long类型的数字转换为double类型的数字
+ double targetLongitude = (double) targetLongitudeLongValue / 10000000;
+ System.out.println("目标经度:" + targetLongitude);
+ // 获取目标纬度
+ byte[] targetLatitudeData = radarDataHelper.extractHexData(dataSegmentHexString, 32, 4);
+ String targetLatitudeHexString = radarDataHelper.byteArrayToHexStringAll(targetLatitudeData);
+
+ // 将十六进制字符串转换为long类型的数字
+ long targetLatitudeLongValue = Long.parseLong(targetLatitudeHexString, 16);
+ // 将long类型的数字转换为double类型的数字
+ double targetLatitude = (double) targetLatitudeLongValue / 10000000;
+ System.out.println("目标纬度:" + targetLatitude);
+ }
+ }
+ }
+ // 如果是交通流量数据
+ else if (objectTypeHexString.equals("03")) {
+ if (dataLength >= 22) {
+ byte[] dataBytes = radarDataHelper.hexStringToByteArray(segment);
+ // 22个字节为一组数据 进行分组解析
+ for (int i = 0; i < dataBytes.length; i += 22) {
+ byte[] dataSegment = Arrays.copyOfRange(dataBytes, i, i + 22);
+ String dataSegmentHexString = radarDataHelper.byteArrayToHexStringAll(dataSegment);
+ System.out.println("数据:" + dataSegmentHexString);
+ // 获取统计周期
+ byte[] statisticsCycleData = radarDataHelper.extractHexData(dataSegmentHexString, 0, 2);
+ String statisticsCycleHexString = radarDataHelper.byteArrayToHexStringAll(statisticsCycleData);
+ // 将16进制数转换为10进制数
+ int statisticsCycle = Integer.parseInt(statisticsCycleHexString, 16);
+ System.out.println("统计周期:" + statisticsCycle);
+ // 获取实时车流量
+ byte[] realTimeTrafficFlowData = radarDataHelper.extractHexData(dataSegmentHexString, 12, 2);
+ String realTimeTrafficFlowHexString = radarDataHelper.byteArrayToHexStringAll(realTimeTrafficFlowData);
+ // 将16进制数转换为10进制数
+ int realTimeTrafficFlow = Integer.parseInt(realTimeTrafficFlowHexString, 16);
+ System.out.println("总流量:" + realTimeTrafficFlow);
+ // 获取平均车速
+ byte[] averageSpeedData = radarDataHelper.extractHexData(dataSegmentHexString, 14, 2);
+ String averageSpeedHexString = radarDataHelper.byteArrayToHexStringAll(averageSpeedData);
+ double averageSpeed = Integer.parseInt(averageSpeedHexString, 16) * 0.1;
+ System.out.println("平均速度:" + averageSpeed);
+ // 获取最大排队长度
+ byte[] maxQueueLengthData = radarDataHelper.extractHexData(dataSegmentHexString, 20, 2);
+ String maxQueueLengthHexString = radarDataHelper.byteArrayToHexStringAll(maxQueueLengthData);
+ // 将16进制数转换为10进制数
+ int maxQueueLength = Integer.parseInt(maxQueueLengthHexString, 16);
+ System.out.println("最大排队长度:" + maxQueueLength);
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ public void test2(){
+ String data = "\\x55\\xaa\\x55\\xbb\\x01\\x88\\x01\\x0121626098\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x18\\x0b\\x08\\x122\\x1e\\x02W\\x0fF\\x0b\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x07\\x04~\\x15r\\x00Z\\xf9\\xde\\x16\\x1d\\x00\\n\\x00\\xb4\\xff\\xa6G\\x86\\x90\\xa8\\x11\\xe1\\xb6C\\x0f^\\r\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x08\\x01\\xd66\\xe2\\x00\\x00\\xf8\\xd0\\x19\\xdf\\x06\\xa4\\x00\\x00\\x00\\x00G\\x86\\x8d\\xe7\\x11\\xe1\\xd4M\\x0fG\\x0c\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x07\\x02N.\\xf4\\x00\\x00\\xf9>\\x18S\\x00\\x00\\xfe\\xf2\\x00\\x00G\\x86\\x8ec\\x11\\xe1\\xcd-\\x0fH\\x0c\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x07\\x02\\xa8\\x18V\\x00\\x1e\\xf9 \\x18\\xc0\\x00\\x00\\x00\\x00\\x00\\x00G\\x86\\x8e\\xc1\\x11\\xe1\\xb8\\xdc\\x0f]\\r\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x08\\x01\\xa4c\\xe2\\x00\\x00\\xf7\\xea\\x1d\\x1c\\x00\\x00\\x00\\x00\\x00\\x00G\\x86\\x8d\\xb3\\x11\\xe1\\xfc\\xb9\\x0fA\\x0c\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x07\\x03H\\x11D\\x00F\\xf9\\x84\\x17]\\xff\\xec\\x00\\x00\\x01\\x0eG\\x86\\x8fg\\x11\\xe1\\xb2\\x82\\x0f@\\x0b\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x07\\x04V\\x1e2\\x00x\\xfaL\\x14\\x99\\x00\\x00\\x04\\xb0\\x00\\x00G\\x86\\x90\\x7f\\x11\\xe1\\xbe\\x1f\\x0fP\\x0b\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x07\\x04`\\\\\\xbc\\x00F\\xf9\\xa2\\x16\\xf1\\x00\\x00\\x00\\x00\\x00\\x00G\\x86\\x90\\x89\\x11\\xe1\\xf6N\\x0f\\x1d\\x0b\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x07\\x03\\xca\\'\\x1a\\xff\\xe2\\xfa8\\x14\\xd1\\x00\\x00\\x01r\\x00\\x00G\\x86\\x8f\\xee\\x11\\xe1\\xc6 \\x0f\\\\\\x0c\\x022\\x14\\x0f\\xff\\xff\\xff\\x07\\x08\\x03>j\"\\x00\\x00\\xf9z\\x17|\\x00\\x00\\x00\\x00\\x00\\x00G\\x86\\x8f\\\\\\x11\\xe2\\x02W\\x55\\xcc\\x55\\xdd'";
+ // 替换 \n、\r、\t
+ String dataString = data.replaceAll("\\\\n", "").replaceAll("\\\\r", "").replaceAll("\\\\t", "").replaceAll(" ", "");
+ System.out.println(dataString);
+ // 以\x为分割符将数据分组
+ String[] segments = dataString.split("\\\\x");
+ // 对每个字符串做分析 前两个字符作为为一个16进制字节 添加到字节数组中 如果还有剩余的字符 每个字符作为一个字节添加到字节数组中
+ LinkedList byteArrayList = new LinkedList<>();
+ for (int i = 0; i < segments.length; i++) {
+ String segment = segments[i];
+ if (segment.length() >= 2) {
+ // 截取前两个字符
+ String hexString = segment.substring(0, 2);
+ // 将两位的字符转换为十六进制的字节
+ byte b = hexStringToByte(hexString);
+ byte[] bytes1 = {b};
+ byteArrayList.add(bytes1);
+
+ // 截取剩余的字符串
+ String remainingString = segment.substring(2);
+ // 如果是设备编号
+ if (remainingString.equals("21626098") || remainingString.equals("21626092") ||
+ remainingString.equals("21626163") || remainingString.equals("21626114") ||
+ remainingString.equals("21626107") || remainingString.equals("21626197")) {
+ // 每个字符转换为ASIC码
+ for (int j = 0; j < remainingString.length(); j++) {
+ byte asic = charToAsciiByte(remainingString.substring(j, j + 1));
+ byte[] bytes2 = new byte[]{asic};
+ byteArrayList.add(bytes2);
+ }
+ }else {
+ // 每个字符作为一个字节添加到字节数组列表中
+ for (int j = 0; j < remainingString.length(); j++) {
+ byte asic = charToAsciiByte(remainingString.substring(j, j + 1));
+ byte[] bytes2 = new byte[]{asic};
+ byteArrayList.add(bytes2);
+ }
+ }
+ }else if (segment.length() == 1){
+ byte asic = charToAsciiByte(segment);
+ byte[] bytes2 = new byte[]{asic};
+ byteArrayList.add(bytes2);
+ }else {
+ System.out.println("无效的字符串:" + segment);
+ }
+ }
+ // 将字节数组列表转换为字节数组
+ byte[] mergedData;
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+ for (byte[] bytes : byteArrayList) {
+ outputStream.write(bytes);
+ }
+ mergedData = outputStream.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to merge byte arrays", e);
+ }
+
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : mergedData) {
+ String hex = Integer.toHexString(0xFF & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+
+ System.out.println(hexString);
+
+ }
+
+ public static byte charToAsciiByte(String charStr) {
+ if (charStr == null || charStr.length() != 1) {
+ throw new IllegalArgumentException("Input string must be one character long.");
+ }
+ // 获取字符的ASCII码值
+ int asciiValue = charStr.charAt(0);
+ // 将ASCII码值转换为字节
+ return (byte) asciiValue;
+ }
+
+ public static byte hexStringToByte(String hexString) {
+ if (hexString == null || hexString.length() != 2) {
+ throw new IllegalArgumentException("Input string must be two characters long.");
+ }
+ // 将字符串转换为整数,并指定基数为16
+ int intValue = Integer.parseInt(hexString, 16);
+ // 将整数转换为字节
+ return (byte) intValue;
+ }
+
+ @Test
+ public void test3(){
+ // 给定的转义过后的字节数据
+ byte[] data = new byte[] {
+ (byte) 0x55, (byte) 0xaa, (byte) 0x55, (byte) 0xbb,
+ 0x01, (byte) 0x88, 0x01, 0x01,
+ '2', '1', '6', '2', '6', '0', '9', '8',
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, (byte) 0x18, 0x0b, 0x08, (byte) 0x12,
+ '2', (byte) 0x1e, 0x02, 'W', (byte) 0x0f, 'F', (byte) 0x0b, 0x02,
+ '2', (byte) 0x14, (byte) 0x0f, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x07, 0x07,
+ 0x04, '~', (byte) 0x15, 'r', 0x00, 'Z', (byte) 0xf9, (byte) 0xde,
+ 0x16, (byte) 0x1d, 0x00, 0x0a, 0x00, (byte) 0xb4, (byte) 0xff, (byte) 0xa6,
+ 'G', (byte) 0x86, (byte) 0x90, (byte) 0xa8, (byte) 0x11, (byte) 0xe1, (byte) 0xb6, 'C',
+ 0x0f, '^', 0x0d, 0x02, '2', (byte) 0x14, (byte) 0x0f, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, 0x07, 0x08, 0x01, (byte) 0xd6, '6', (byte) 0xe2,
+ 0x00, 0x00, (byte) 0xf8, (byte) 0xd0, (byte) 0x19, (byte) 0xdf, 0x06, (byte) 0xa4,
+ 0x00, 0x00, 0x00, 0x00, 'G', (byte) 0x86, (byte) 0x8d, (byte) 0xe7,
+ (byte) 0x11, (byte) 0xe1, (byte) 0xd4, 'M', 0x0f, 'G', 0x0c, 0x02,
+ '2', (byte) 0x14, (byte) 0x0f, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x07, 0x07,
+ 0x02, 'N', '.', (byte) 0xf4, 0x00, 0x00, (byte) 0xf9, '>',
+ (byte) 0x18, 'S', 0x00, 0x00, (byte) 0xfe, (byte) 0xf2, 0x00, 0x00,
+ 'G', (byte) 0x86, (byte) 0x8e, 'c', (byte) 0x11, (byte) 0xe1, (byte) 0xcd, '-',
+ 0x0f, 'H', 0x0c, 0x02, '2', (byte) 0x14, (byte) 0x0f, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, 0x07, 0x07, 0x02, (byte) 0xa8, 0x18, 'V',
+ 0x00, (byte) 0x1e, (byte) 0xf9, ' ', (byte) 0x18, (byte) 0xc0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 'G', (byte) 0x86, (byte) 0x8e, (byte) 0xc1,
+ (byte) 0x11, (byte) 0xe1, (byte) 0xb8, (byte) 0xdc, 0x0f, ']',
+ 0x0d, 0x02, '2', (byte) 0x14, (byte) 0x0f, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, 0x07, 0x08, 0x01, (byte) 0xa4, 'c', (byte) 0xe2, 0x00,
+ 0x00, (byte) 0xf7, (byte) 0xea, (byte) 0x1d, (byte) 0x1c, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 'G', (byte) 0x86, (byte) 0x8d, (byte) 0xb3,
+ (byte) 0x11, (byte) 0xe1, (byte) 0xfc, (byte) 0xb9, 0x0f, 'A',
+ 0x0c, 0x02, '2', (byte) 0x14, (byte) 0x0f, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, 0x07, 0x07, 0x03, 'H', 0x11, 'D', 0x00,
+ 'F', (byte) 0xf9, (byte) 0x84, 0x17, ']', (byte) 0xff, (byte) 0xec, 0x00,
+ 0x00, 0x01, 0x0e, 'G', (byte) 0x86, (byte) 0x8f, 'g', (byte) 0x11,
+ (byte) 0xe1, (byte) 0xb2, (byte) 0x82, 0x0f, '@', 0x0b, 0x02, '2',
+ (byte) 0x14, (byte) 0x0f, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x07, 0x07, 0x04,
+ 'V', 0x1e, '2', 0x00, 'x', (byte) 0xfa, 'L', 0x14,
+ (byte) 0x99, 0x00, 0x00, 0x04, (byte) 0xb0, 0x00, 0x00, 'G',
+ (byte) 0x86, (byte) 0x90, (byte) 0x7f, (byte) 0x11, (byte) 0xe1, (byte) 0xbe, 0x1f, 0x0f,
+ 'P', 0x0b, 0x02, '2', (byte) 0x14, (byte) 0x0f, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, 0x07, 0x07, 0x04, '`', '\\', (byte) 0xbc, 0x00,
+ 'F', (byte) 0xf9, (byte) 0xa2, 0x16, (byte) 0xf1, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 'G', (byte) 0x86, (byte) 0x90, (byte) 0x89, (byte) 0x11,
+ (byte) 0xe1, (byte) 0xf6, 'N', 0x0f, 0x1d, 0x0b, 0x02, '2',
+ (byte) 0x14, (byte) 0x0f, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x07, 0x07, 0x03,
+ (byte) 0xca, 0x27, 0x1a, (byte) 0xff, (byte) 0xe2, (byte) 0xfa, '8', 0x14,
+ (byte) 0xd1, 0x00, 0x00, 0x01, 'r', 0x00, 0x00, 'G',
+ (byte) 0x86, (byte) 0x8f, (byte) 0xee, (byte) 0x11, (byte) 0xe2, 0x02, 'W', 'U',
+ (byte) 0xcc, 'U', (byte) 0xdd
+ };
+
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : data) {
+ String hex = Integer.toHexString(0xFF & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+
+ System.out.println(hexString.toString());
+ }
+
+ private String getLocalDateTime(String segment, byte[] timestampData) {
+ // 第一个字节 -> year
+ byte year = timestampData[0];
+ // 将16进制字节转换为整数
+ int yearInt = Integer.parseInt(String.format("%02X", year), 16);
+ // 创建位掩码来提取第0到7位
+ int yearMask = 0xFF;
+ // 使用位掩码提取第0到第7位
+ int extractedYearBits = yearInt & yearMask;
+ // 数据 0~255 对应 2000~2255 year 获取实际的年份
+ int yearValue = extractedYearBits + 2000;
+ System.out.println("年份:" + yearValue);
+
+ // 第二个字节 -> month
+ byte month = timestampData[1];
+ // 将16进制字节转换为整数
+ int monthInt = Integer.parseInt(String.format("%02X", month), 16);
+ // 创建位掩码来提取第0到3位
+ // 1~12 对应 1~12 month
+ int monthMask = 0x0F;
+ // 使用位掩码提取第0到第3位
+ int monthValue = monthInt & monthMask;
+ System.out.println("月份:" + monthValue);
+
+ // 第三个字节 -> day
+ byte day = timestampData[2];
+ // 将16进制字节转换为整数
+ int dayInt = Integer.parseInt(String.format("%02X", day), 16);
+ // 创建位掩码来提取第0到4位
+ // 数据 1~31 对应 1~31 day
+ int dayMask = 0x1F;
+ // 使用位掩码提取第0到第4位
+ // 数据 1~31 对应 1~31 day
+ int dayValue = dayInt & dayMask;
+ System.out.println("天:" + dayValue);
+
+ // 第四个字节 -> hour
+ byte hour = timestampData[3];
+ // 将16进制字节转换为整数
+ int hourInt = Integer.parseInt(String.format("%02X", hour), 16);
+ // 创建位掩码来提取第0到4位
+ int hourMask = 0x1F;
+ // 使用位掩码提取第0到第4位
+ // 数据 0~31 对应 0~23 hour
+ int hourValue = hourInt & hourMask;
+ System.out.println("小时:" + hourValue);
+
+ // 第五个字节 -> minute
+ byte minute = timestampData[4];
+ // 将16进制字节转换为整数
+ int minuteInt = Integer.parseInt(String.format("%02X", minute), 16);
+ // 创建位掩码来提取第0到5位
+ int minuteMask = 0x3F;
+ // 使用位掩码提取第0到第5位
+ // 数据 0~63 对应 0~59 minute
+ int minuteValue = minuteInt & minuteMask;
+ System.out.println("分钟:" + minuteValue);
+
+ // 第六个字节 -> second
+ byte second = timestampData[5];
+ // 将16进制字节转换为整数
+ int secondInt = Integer.parseInt(String.format("%02X", second), 16);
+ // 创建位掩码来提取第0到5位
+ int secondMask = 0x3F;
+ // 使用位掩码提取第0到第5位
+ // 数据 0~63 对应 0~59 second
+ int secondValue = secondInt & secondMask;
+ System.out.println("秒:" + secondValue);
+ // 第7、8个字节-> millisecond
+ // 把第7和第8个字节拼接成16进制字符串 再转换为整数
+ byte[] millisecondData = radarDataHelper.extractHexData(segment, 30, 2);
+ String millisecondHexString = radarDataHelper.byteArrayToHexString(millisecondData);
+ int millisecondValue = Integer.parseInt(millisecondHexString, 16);
+ // 创建位掩码来提取第0到9位
+ int millisecondMask = 0x3FF;
+ // 使用位掩码提取第0到第9位
+ millisecondValue = millisecondValue & millisecondMask;
+ // 数据 0~999 对应 0~999 毫秒
+ System.out.println("毫秒:" + millisecondValue);
+ // 拼接成LocalDateTime 类型的数据
+ String localDateTime = yearValue + "-" + monthValue + "-" + dayValue + " " + hourValue + ":" + minuteValue + ":" + secondValue;
+ return localDateTime;
+ }
+}
diff --git a/picture/1.jpg b/picture/1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4525ae2141b5328af98552bdfb71013daff194d5
GIT binary patch
literal 285033
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