Browse Source

Merge remote-tracking branch 'origin/develop' into develop

develop
wangsixiang 1 year ago
parent
commit
b1b69e3e10
  1. 2
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
  2. 25
      zc-business/pom.xml
  3. 19
      zc-business/src/main/java/com/zc/business/constant/RedisKeyConstants.java
  4. 27
      zc-business/src/main/java/com/zc/business/constant/StakeMarkConstant.java
  5. 14
      zc-business/src/main/java/com/zc/business/constant/StatisticalRecoveryOffsetTime.java
  6. 60
      zc-business/src/main/java/com/zc/business/controller/DcTrafficStatisticsController.java
  7. 9
      zc-business/src/main/java/com/zc/business/controller/VideoController.java
  8. 1050
      zc-business/src/main/java/com/zc/business/controller/WordController.java
  9. 20
      zc-business/src/main/java/com/zc/business/domain/DcDevice.java
  10. 69
      zc-business/src/main/java/com/zc/business/domain/DcTrafficMetricsData.java
  11. 34
      zc-business/src/main/java/com/zc/business/domain/DcTrafficVolumeForecast.java
  12. 16
      zc-business/src/main/java/com/zc/business/enums/CameraDirectionEnum.java
  13. 98
      zc-business/src/main/java/com/zc/business/enums/ChannelCongestionLevelEnum.java
  14. 4
      zc-business/src/main/java/com/zc/business/enums/DeviceDataCategoryEnum.java
  15. 10
      zc-business/src/main/java/com/zc/business/enums/LaneDirectionEnum.java
  16. 48
      zc-business/src/main/java/com/zc/business/enums/VehicleLoadSaturationFactorEnum.java
  17. 93
      zc-business/src/main/java/com/zc/business/mapper/DcTrafficIncidentsMapper.java
  18. 18
      zc-business/src/main/java/com/zc/business/mapper/DcTrafficVolumeForecastMapper.java
  19. 2
      zc-business/src/main/java/com/zc/business/message/device/handler/DeviceMessageHandler.java
  20. 44
      zc-business/src/main/java/com/zc/business/request/DcTrafficMetricsDataRequest.java
  21. 15
      zc-business/src/main/java/com/zc/business/service/DcTrafficSectionDataService.java
  22. 38
      zc-business/src/main/java/com/zc/business/service/DcTrafficStatisticsService.java
  23. 7
      zc-business/src/main/java/com/zc/business/service/DcTrafficVolumeForecastService.java
  24. 59
      zc-business/src/main/java/com/zc/business/service/impl/DcRoadSectionServiceImpl.java
  25. 324
      zc-business/src/main/java/com/zc/business/service/impl/DcTrafficStatisticsServiceImpl.java
  26. 19
      zc-business/src/main/java/com/zc/business/service/impl/DcTrafficVolumeForecastServiceImpl.java
  27. 79
      zc-business/src/main/java/com/zc/business/statistics/handler/RealtimeTrafficStatistics.java
  28. 635
      zc-business/src/main/java/com/zc/business/statistics/handler/TrafficAnalysis.java
  29. 257
      zc-business/src/main/java/com/zc/business/statistics/handler/TrafficStatistics.java
  30. 54
      zc-business/src/main/java/com/zc/business/utils/AlgorithmUtils.java
  31. 369
      zc-business/src/main/java/com/zc/business/utils/PoiUtil.java
  32. 37
      zc-business/src/main/java/com/zc/business/utils/StakeMarkUtils.java
  33. 229
      zc-business/src/main/resources/mapper/business/DcTrafficIncidentsMapper.xml
  34. BIN
      zc-business/src/main/resources/wordTemplate/chartTemplate.docx
  35. BIN
      zc-business/src/main/resources/wordTemplate/chartTemplate2.docx
  36. BIN
      zc-business/src/main/resources/wordTemplate/wordTemplate.docx

2
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java

@ -69,7 +69,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public boolean matches(HttpServletRequest request) {
// 获取请求的IP
String requestIP = request.getRemoteAddr();
String requestIP = request.getHeader("X-Forwarded-For");
// 将配置文件中的IP字符串分割为数组
String[] ips = allowedIPs.split(",");
// 检查请求的IP是否在允许的IP数组中

25
zc-business/pom.xml

@ -47,6 +47,31 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<!--2.poi官网指出需要poi4.x.x版本抛弃了jdk1.7之前的版本,所以适应此版本需要将jdk升级,如果不想升级还有另一种办法就是,
使用springBoot单独做一个服务为你的主项目提供一个接口,让主项目去调用生成word流让主项目去接收即可。-->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.9.1</version>
</dependency>
</dependencies>
<groupId>com.zc</groupId>
<version>1.0.0</version>

19
zc-business/src/main/java/com/zc/business/constant/RedisKeyConstants.java

@ -10,4 +10,23 @@ public class RedisKeyConstants
* 设备
*/
public static final String DC_DEVICES = "dc:devices";
/**
* 路段
*/
public static final String DC_ROAD_SECTION = "dc:roadSection";
/**
* 设备实时交通统计
*/
private static final String DC_DEVICES_TRAFFIC_STATISTICS = "dc:devicesTrafficStatistics";
/**
* 获取实时交通统计key
* @param direction 方向
* @return key
*/
public static String getDcDevicesTrafficStatisticsKey(Byte direction) {
return DC_DEVICES_TRAFFIC_STATISTICS + ":" + direction;
}
}

27
zc-business/src/main/java/com/zc/business/constant/StakeMarkConstant.java

@ -0,0 +1,27 @@
package com.zc.business.constant;
/**
* StakeMarkConstant类用于定义一些桩号常量
* @author xiepufeng
*/
public class StakeMarkConstant {
// 定义最大桩号常量
public static final int MAX_STAKE_MARK = 208979;
// 定义最小桩号常量
public static final int MIN_STAKE_MARK = 54394;
// 定义毫米波雷达最大测量间隔
public static final int MAX_INTERVAL_MILLIMETER_WAVE_RADAR = 12030;
/**
* 计算道路长度
* @return 道路长度单位
*/
public static int calculateRoadLength() {
return MAX_STAKE_MARK - MIN_STAKE_MARK;
}
}

14
zc-business/src/main/java/com/zc/business/constant/StatisticalRecoveryOffsetTime.java

@ -1,14 +0,0 @@
package com.zc.business.constant;
/**
* 统计恢复偏移时间类
* 用于定义与交通数据恢复相关的偏移时间常量
*
* @author xiepufeng
*/
public class StatisticalRecoveryOffsetTime {
// 定义交通数据段偏移的天数常量,表示偏移-10天
public static final int TRAFFIC_SECTION_DATA_OFFSET_DAY = -10;
}

60
zc-business/src/main/java/com/zc/business/controller/DcTrafficStatisticsController.java

@ -0,0 +1,60 @@
package com.zc.business.controller;
import com.ruoyi.common.core.domain.AjaxResult;
import com.zc.business.domain.DcTrafficMetricsData;
import com.zc.business.request.DcTrafficMetricsDataRequest;
import com.zc.business.service.DcTrafficStatisticsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 交通数据统计
*
* @author xiepufeng
*/
@Api(tags = "交通数据统计")
@RestController
@RequestMapping("/business/traffic-statistics")
public class DcTrafficStatisticsController {
@Autowired
private DcTrafficStatisticsService dcTrafficStatisticsService;
/**
* 获取当前交通特征指数
*
* @param request 请求参数封装了获取交通指标所需的数据和条件
* @return 返回当前交通特征指数的数据结果使用AjaxResult包装
*/
@ApiOperation("获取当前交通特征指数")
@GetMapping("/current/metrics")
public AjaxResult currentTrafficMetrics(DcTrafficMetricsDataRequest request){
// 调用服务层方法,获取当前交通指标数据
DcTrafficMetricsData dcTrafficMetricsData = dcTrafficStatisticsService.currentTrafficMetrics(request);
// 将获取到的交通指标数据封装为成功的结果并返回
return AjaxResult.success(dcTrafficMetricsData);
}
/**
* 获取历史交通特征指数
*
* @param request 请求参数包含需要查询的历史交通特征指数的详细信息
* @return 返回一个AjaxResult对象其中包含了查询结果的成功状态和历史交通特征指数数据列表
*/
@ApiOperation("获取历史交通特征指数")
@GetMapping("/history/metrics")
public AjaxResult historyTrafficMetrics(DcTrafficMetricsDataRequest request){
// 调用服务层方法,查询历史交通特征指数数据
List<DcTrafficMetricsData> dcTrafficMetricsDataList = dcTrafficStatisticsService.historyTrafficMetrics(request);
// 将查询结果封装成成功响应并返回
return AjaxResult.success(dcTrafficMetricsDataList);
}
}

9
zc-business/src/main/java/com/zc/business/controller/VideoController.java

@ -9,8 +9,8 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.zc.business.constant.DeviceTypeConstants;
import com.zc.business.domain.DcDevice;
import com.zc.business.enums.CameraDirection;
import com.zc.business.enums.LaneDirection;
import com.zc.business.enums.CameraDirectionEnum;
import com.zc.business.enums.LaneDirectionEnum;
import com.zc.business.service.IDcDeviceService;
import com.zc.business.service.IMiddleDatabaseService;
import com.zc.common.core.httpclient.OkHttp;
@ -26,7 +26,6 @@ import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
@ -121,12 +120,12 @@ public class VideoController extends BaseController {
String pileNum = cameraInfo.getString("pileNum");
// 方向
Integer camOrientation = cameraInfo.getInteger("camOrientation");
LaneDirection laneDirection = CameraDirection.fromCode(camOrientation).toLaneDirection();
LaneDirectionEnum laneDirectionEnum = CameraDirectionEnum.fromCode(camOrientation).toLaneDirection();
// 是否有云台控制 0 有(球机) 1 ⽆(枪机)
String ptzCtrl = cameraInfo.getString("ptzCtrl");
String key = pileNum + "|" + laneDirection.getValue() + "|" + ptzCtrl;
String key = pileNum + "|" + laneDirectionEnum.getValue() + "|" + ptzCtrl;
if (cameraMap.containsKey(key)) {
DcDevice dcDevice = cameraMap.get(key);

1050
zc-business/src/main/java/com/zc/business/controller/WordController.java

File diff suppressed because it is too large

20
zc-business/src/main/java/com/zc/business/domain/DcDevice.java

@ -3,6 +3,7 @@ package com.zc.business.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.zc.business.utils.StakeMarkUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -81,23 +82,6 @@ public class DcDevice {
private String manufacturer;
public Integer stakeMarkToInt() {
if (stakeMark == null) {
return null;
}
// 不区分大小写的正则表达式匹配 'k' 和 '+'
String[] parts = this.stakeMark.split("(?i)k|\\+");
// 提取出公里数部分和米数部分
String kmStr = parts[1].trim();
// 将公里数和米数转换为整数
int km = Integer.parseInt(kmStr);
int m = 0;
if (parts.length == 3) {
String mStr = parts[2].trim();
m = Integer.parseInt(mStr);
}
// 计算总米数
return km * 1000 + m;
return StakeMarkUtils.stakeMarkToInt(stakeMark);
}
}

69
zc-business/src/main/java/com/zc/business/domain/DcTrafficMetricsData.java

@ -0,0 +1,69 @@
package com.zc.business.domain;
import lombok.Data;
import java.util.Date;
/**
* 交通特征指数定义
* @author xiepufeng
*/
@Data
public class DcTrafficMetricsData {
/**
* 所属辖区路段
*/
private Long roadSectionId;
/**
* 交通组成特征指数
*/
private Integer trafficFeature;
/**
* 通道拥挤度
*/
private Integer channelCongestionLevel;
/**
* 路网拥挤度
*/
private Integer roadNetworkCongestionLevel;
/**
* 饱和度
*/
private Integer saturationLevel;
/**
* 统计时间
*/
private Date statisticalDate;
/**
* 道路方向
*/
private Byte direction;
/**
* 时段类型
* 1- 2- 3- 4-
*/
private Byte periodType;
/**
* 交通量
*/
private Integer trafficVolume;
/**
* 拥堵路段数量
*/
private Integer congestedSectionQuantity;
/**
* 拥堵里程
*/
private Integer congestedDistance;
}

34
zc-business/src/main/java/com/zc/business/domain/DcTrafficVolumeForecast.java

@ -0,0 +1,34 @@
package com.zc.business.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
/**
* 路段设计交通量预测数据定义
* @author xiepufeng
*/
@Data
public class DcTrafficVolumeForecast {
/**
* 主键
*/
@TableId
private Long id;
/**
* 所属路段
*/
private Long roadSectionId;
/**
* 年份
*/
private Integer year;
/**
* 设计交通量
*/
private Integer designTrafficVolume;
}

16
zc-business/src/main/java/com/zc/business/enums/CameraDirection.java → zc-business/src/main/java/com/zc/business/enums/CameraDirectionEnum.java

@ -3,7 +3,7 @@ package com.zc.business.enums;
/**
* 摄像头方向
*/
public enum CameraDirection {
public enum CameraDirectionEnum {
/**
* 摄像头上行
*/
@ -20,7 +20,7 @@ public enum CameraDirection {
private final int code;
private final String description;
CameraDirection(int code, String description) {
CameraDirectionEnum(int code, String description) {
this.code = code;
this.description = description;
}
@ -34,8 +34,8 @@ public enum CameraDirection {
}
// 可选:根据code获取枚举值
public static CameraDirection fromCode(int code) {
for (CameraDirection direction : values()) {
public static CameraDirectionEnum fromCode(int code) {
for (CameraDirectionEnum direction : values()) {
if (direction.getCode() == code) {
return direction;
}
@ -44,15 +44,15 @@ public enum CameraDirection {
}
public LaneDirection toLaneDirection() {
public LaneDirectionEnum toLaneDirection() {
switch (this) {
case UPWARD:
return LaneDirection.UPWARD;
return LaneDirectionEnum.UPWARD;
case DOWN:
return LaneDirection.DOWNWARD;
return LaneDirectionEnum.DOWNWARD;
case BIDIRECTIONAL:
// 假设摄像机双向就映射为车道双向
return LaneDirection.BIDIRECTIONAL;
return LaneDirectionEnum.BIDIRECTIONAL;
default:
throw new IllegalStateException("未知的CameraDirection: " + this);
}

98
zc-business/src/main/java/com/zc/business/enums/ChannelCongestionLevelEnum.java

@ -0,0 +1,98 @@
package com.zc.business.enums;
import lombok.Getter;
/**
* 通道拥挤度等级枚举
* @author xiepufeng
*/
@Getter
public enum ChannelCongestionLevelEnum {
/**
* 表示通道畅通速度阈值为70 km/h
*/
FLOWING(70, 0, "畅通"),
/**
* 表示通道基本畅通速度阈值为50 km/h
*/
BASIC_FLOWING(50, 0, "基本畅通"),
/**
* 表示通道轻度拥堵速度阈值为40 km/h
*/
LIGHT_CONGESTION(40, 1, "轻度拥堵"),
/**
* 表示通道中度拥堵速度阈值为20 km/h
*/
MEDIUM_CONGESTION(20, 2, "中度拥堵"),
/**
* 使用负数作为默认值表示无限小始终小于其他速度阈值表示通道严重拥堵
*/
SEVERE_CONGESTION(-1, 4, "严重拥堵");
/**
* 速度阈值用于判断通道拥挤程度
*/
private final int speedThreshold;
/**
* 默认的拥堵里程数
*/
private final int defaultCongestionDistance;
/**
* 对拥挤度等级的描述
*/
private final String description;
/**
* 构造函数初始化通道拥挤度等级
*
* @param speedThreshold 速度阈值
* @param description 等级描述
*/
ChannelCongestionLevelEnum(int speedThreshold, int defaultCongestionMiles, String description) {
this.speedThreshold = speedThreshold;
this.defaultCongestionDistance = defaultCongestionMiles;
this.description = description;
}
/**
* 根据给定速度返回对应的通道拥挤度等级
*
* @param speed 速度单位km/h
* @return 对应的通道拥挤度等级
*/
public static ChannelCongestionLevelEnum fromSpeed(int speed) {
for (ChannelCongestionLevelEnum level : values()) {
if (speed > level.speedThreshold) {
return level;
}
}
return SEVERE_CONGESTION; // 若速度小于等于所有等级阈值,则视为严重拥堵
}
/**
* 判断速度是否是是中度拥堵或者严重拥堵
*/
public static boolean isMediumOrSevereCongestion(int speed) {
ChannelCongestionLevelEnum level = fromSpeed(speed);
return level == MEDIUM_CONGESTION || level == SEVERE_CONGESTION;
}
/**
* 判断给定速度是否属于指定的拥挤度等级
*
* @param speed 速度单位km/h
* @param level 拥挤度等级
* @return 如果速度属于该等级返回true否则返回false
*/
public static boolean isWithinLevel(int speed, ChannelCongestionLevelEnum level) {
ChannelCongestionLevelEnum currentLevel = fromSpeed(speed);
return currentLevel == level;
}
}

4
zc-business/src/main/java/com/zc/business/enums/DeviceDataCategory.java → zc-business/src/main/java/com/zc/business/enums/DeviceDataCategoryEnum.java

@ -6,13 +6,13 @@ import lombok.Getter;
* @author xiepufeng
*/
@Getter
public enum DeviceDataCategory {
public enum DeviceDataCategoryEnum {
REAL_TIME("实时数据"),
HISTORY("历史数据");
private final String description;
DeviceDataCategory(String description) {
DeviceDataCategoryEnum(String description) {
this.description = description;
}

10
zc-business/src/main/java/com/zc/business/enums/LaneDirection.java → zc-business/src/main/java/com/zc/business/enums/LaneDirectionEnum.java

@ -8,7 +8,7 @@ import lombok.Getter;
* @author xiepufeng
**/
@Getter
public enum LaneDirection {
public enum LaneDirectionEnum {
UPWARD((byte)1, "上行"),
BIDIRECTIONAL((byte) 2, "上下行(双向)"),
@ -17,18 +17,18 @@ public enum LaneDirection {
private final byte value;
private final String description;
LaneDirection(byte value, String description) {
LaneDirectionEnum(byte value, String description) {
this.value = value;
this.description = description;
}
public static LaneDirection fromValue(int value) {
for (LaneDirection direction : values()) {
public static LaneDirectionEnum fromValue(int value) {
for (LaneDirectionEnum direction : values()) {
if (direction.getValue() == value) {
return direction;
}
}
throw new IllegalArgumentException("Invalid value for LaneDirection: " + value);
throw new IllegalArgumentException("无效的LaneDirection值: " + value);
}
}

48
zc-business/src/main/java/com/zc/business/enums/VehicleLoadSaturationFactorEnum.java

@ -0,0 +1,48 @@
package com.zc.business.enums;
import lombok.Getter;
/**
* 饱和度计算车辆折算系数
* @author xiepufeng
*/
@Getter
public enum VehicleLoadSaturationFactorEnum {
/**
* 小客车车辆折算系数1.0座位19座的客车和载质量2t的货车
*/
PASSENGER_CAR(1.0, "小客车,座位≤19座的客车和载质量≤2t的货车"),
/**
* 中型车车辆折算系数1.5座位>19座的客车和2t<载质量7t的货车
*/
MEDIUM_VEHICLE(1.5, "中型车,座位>19座的客车和2t<载质量≤7t的货车"),
/**
* 大型车车辆折算系数2.57t<载质量20t的货车
*/
LARGE_VEHICLE(2.5, "大型车,7t<载质量≤20t的货车"),
/**
* 汽车列车车辆折算系数4.020t<载质量的货车
*/
TRUCK_TRAINS(4.0, "汽车列车,20t<载质量的货车");
private final double conversionFactor;
private final String description;
VehicleLoadSaturationFactorEnum(double conversionFactor, String description) {
this.conversionFactor = conversionFactor;
this.description = description;
}
// 如果需要根据车辆折算系数查找枚举项
public static VehicleLoadSaturationFactorEnum findByConversionFactor(double conversionFactor) {
for (VehicleLoadSaturationFactorEnum type : values()) {
if (type.getConversionFactor() == conversionFactor) {
return type;
}
}
throw new IllegalArgumentException("未知的饱和度计算车辆折算系数:" + conversionFactor);
}
}

93
zc-business/src/main/java/com/zc/business/mapper/DcTrafficIncidentsMapper.java

@ -236,4 +236,97 @@ public interface DcTrafficIncidentsMapper {
*/
List<Map<String,Object>> selectEventTypeAnalysis(@Param("direction") String direction,@Param("type") String type,@Param("startTime") String startTime);
/**
* @Description 交通管制情况
*
* @author liuwenge
* @date 2024/3/27 18:32
* @param startTime
* @param endTime
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
List<Map<String,Object>> selectTrafficControlAnalysis(@Param("startTime") String startTime,@Param("endTime") String endTime);
/**
* @Description 交通事故统计
*
* @author liuwenge
* @date 2024/3/27 20:04
* @param
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
List<Map<String,Object>> selectTrafficAccidentAnalysis();
/**
* @Description 封闭原因统计
*
* @author liuwenge
* @date 2024/3/28 14:47
* @param controlType
* @param startTime
* @param endTime
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
List<Map<String,Object>> selectTrafficControlReasons(@Param("controlType") String controlType,@Param("startTime") String startTime,@Param("endTime") String endTime);
/**
* @Description 封闭数量统计
*
* @author liuwenge
* @date 2024/3/28 14:51
* @param
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
List<Map<String,Object>> selectClosedQuantity();
/**
* @Description 事故类型统计--当月按类型
*
* @author liuwenge
* @date 2024/3/28 15:19
* @param
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
List<Map<String,Object>> selectAccidentTypeStatistics();
/**
* @Description 事故类型统计--当天按小时
*
* @author liuwenge
* @date 2024/3/28 16:28
* @param
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
List<Map<String,Object>> selectTrafficAccidents();
/**
* @Description 拥堵地点
*
* @author liuwenge
* @date 2024/3/28 16:55
* @param
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
List<Map<String,Object>> selectCongestionLocation();
/**
* @Description 拥堵时长
*
* @author liuwenge
* @date 2024/3/28 17:23
* @param
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
List<Map<String,Object>> selectCongestionDuration(@Param("startTime") String startTime,@Param("endTime") String endTime);
/**
* @Description 拥堵长度
*
* @author liuwenge
* @date 2024/3/28 19:30
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
List<Map<String,Object>> selectCongestionMileage();
}

18
zc-business/src/main/java/com/zc/business/mapper/DcTrafficVolumeForecastMapper.java

@ -0,0 +1,18 @@
package com.zc.business.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zc.business.domain.DcTrafficSectionData;
import com.zc.business.domain.DcTrafficVolumeForecast;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
/**
* 路段设计日最大交通量预测数据Mapper接口
*
* @author xiepufeng
*/
@Mapper
public interface DcTrafficVolumeForecastMapper extends BaseMapper<DcTrafficVolumeForecast> {
}

2
zc-business/src/main/java/com/zc/business/message/device/handler/DeviceMessageHandler.java

@ -52,7 +52,7 @@ public class DeviceMessageHandler {
private RedisCache redisCache;
@Resource
private DcTrafficSectionDataService dcTrafficSectionDataService;
private DcTrafficStatisticsService dcTrafficSectionDataService;
@Autowired
private IDcMeteorologicalDetectorDataService meteorologicalDetectorDataService;

44
zc-business/src/main/java/com/zc/business/request/DcTrafficMetricsDataRequest.java

@ -0,0 +1,44 @@
package com.zc.business.request;
import lombok.Data;
import java.util.Date;
/**
* 交通特征指数请求参数定义
* @author xiepufeng
*/
@Data
public class DcTrafficMetricsDataRequest {
/**
* 所属辖区路段
*/
private Long roadSectionId;
/**
* 道路方向
*/
private Byte direction;
/**
* 时段类型
* 1- 2- 3- 4-
*/
private Byte periodType;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
/**
* 是否分路段统计
*/
private boolean segmented;
}

15
zc-business/src/main/java/com/zc/business/service/DcTrafficSectionDataService.java

@ -1,15 +0,0 @@
package com.zc.business.service;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zc.business.domain.DcTrafficSectionData;
public interface DcTrafficSectionDataService extends IService<DcTrafficSectionData> {
/**
* 处理实时接收到的一类交流站设备消息并将其转换为交通断面统计数据对象并缓存
*
* @param msg 设备发送的JSON格式实时消息
*/
void processRealtimeOneStopMessage(JSONObject msg);
}

38
zc-business/src/main/java/com/zc/business/service/DcTrafficStatisticsService.java

@ -0,0 +1,38 @@
package com.zc.business.service;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zc.business.domain.DcTrafficMetricsData;
import com.zc.business.domain.DcTrafficSectionData;
import com.zc.business.request.DcTrafficMetricsDataRequest;
import java.util.List;
public interface DcTrafficStatisticsService extends IService<DcTrafficSectionData> {
/**
* 处理实时接收到的一类交流站设备消息并将其转换为交通断面统计数据对象并缓存
*
* @param msg 设备发送的JSON格式实时消息
*/
void processRealtimeOneStopMessage(JSONObject msg);
/**
* 根据提供的请求参数获取当前的流量指标数据
*
* @param request 包含获取流量指标所需的所有请求参数的对象
* @return DcTrafficMetricsData 返回一个包含当前流量指标数据的对象
* 该对象包含了关于数据中心网络流量的各种度量指标
*/
DcTrafficMetricsData currentTrafficMetrics(DcTrafficMetricsDataRequest request);
/**
* 获取历史流量指标数据列表
*
* @param request 包含获取流量指标所需的所有请求参数的对象
* @return 返回符合查询条件的历史流量指标数据列表
*/
List<DcTrafficMetricsData> historyTrafficMetrics(DcTrafficMetricsDataRequest request);
}

7
zc-business/src/main/java/com/zc/business/service/DcTrafficVolumeForecastService.java

@ -0,0 +1,7 @@
package com.zc.business.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zc.business.domain.DcTrafficVolumeForecast;
public interface DcTrafficVolumeForecastService extends IService<DcTrafficVolumeForecast> {
}

59
zc-business/src/main/java/com/zc/business/service/impl/DcRoadSectionServiceImpl.java

@ -5,7 +5,10 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.DateUtils;
import com.zc.business.constant.RedisKeyConstants;
import com.zc.business.domain.DcDevice;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -14,6 +17,9 @@ import com.zc.business.domain.DcRoadSection;
import com.zc.business.service.IDcRoadSectionService;
import org.w3c.dom.CDATASection;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/**
* 辖区路段Service业务层处理
*
@ -26,6 +32,34 @@ public class DcRoadSectionServiceImpl implements IDcRoadSectionService
@Autowired
private DcRoadSectionMapper dcRoadSectionMapper;
@Resource
private RedisCache redisCache;
@PostConstruct
public void init() {
/*
添加数据到 redis 缓存
*/
updateRedisCache();
}
/**
* 添加数据到 redis 缓存
*/
public void updateRedisCache() {
// 获取全部设备列表数据
List<DcRoadSection> dcRoadSections = dcRoadSectionMapper.selectDcRoadSectionList(null);
if (dcRoadSections == null || dcRoadSections.isEmpty()) {
return;
}
// 清楚 redis 缓存数据
redisCache.deleteObject(RedisKeyConstants.DC_ROAD_SECTION);
// 添加 redis 缓存数据
dcRoadSections.forEach(val -> redisCache.setCacheMapValue(RedisKeyConstants.DC_ROAD_SECTION, val.getId(), val));
}
/**
* 查询辖区路段
*
@ -108,7 +142,12 @@ public class DcRoadSectionServiceImpl implements IDcRoadSectionService
public int insertDcRoadSection(DcRoadSection dcRoadSection)
{
dcRoadSection.setCreateTime(DateUtils.getNowDate());
return dcRoadSectionMapper.insertDcRoadSection(dcRoadSection);
int result = dcRoadSectionMapper.insertDcRoadSection(dcRoadSection);
if (result > 0) {
updateRedisCache();
}
return result;
}
/**
@ -121,7 +160,11 @@ public class DcRoadSectionServiceImpl implements IDcRoadSectionService
public int updateDcRoadSection(DcRoadSection dcRoadSection)
{
dcRoadSection.setUpdateTime(DateUtils.getNowDate());
return dcRoadSectionMapper.updateDcRoadSection(dcRoadSection);
int result = dcRoadSectionMapper.updateDcRoadSection(dcRoadSection);
if (result > 0) {
updateRedisCache();
}
return result;
}
/**
@ -133,7 +176,11 @@ public class DcRoadSectionServiceImpl implements IDcRoadSectionService
@Override
public int deleteDcRoadSectionByIds(Long[] ids)
{
return dcRoadSectionMapper.deleteDcRoadSectionByIds(ids);
int result = dcRoadSectionMapper.deleteDcRoadSectionByIds(ids);
if (result > 0) {
updateRedisCache();
}
return result;
}
/**
@ -145,7 +192,11 @@ public class DcRoadSectionServiceImpl implements IDcRoadSectionService
@Override
public int deleteDcRoadSectionById(Long id)
{
return dcRoadSectionMapper.deleteDcRoadSectionById(id);
int result = dcRoadSectionMapper.deleteDcRoadSectionById(id);
if (result > 0) {
updateRedisCache();
}
return result;
}
//查询路线id与名称
@Override

324
zc-business/src/main/java/com/zc/business/service/impl/DcTrafficStatisticsServiceImpl.java

@ -0,0 +1,324 @@
package com.zc.business.service.impl;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.zc.business.constant.RedisKeyConstants;
import com.zc.business.controller.DcDeviceController;
import com.zc.business.domain.DcRoadSection;
import com.zc.business.domain.DcTrafficMetricsData;
import com.zc.business.domain.DcTrafficSectionData;
import com.zc.business.enums.*;
import com.zc.business.request.DcTrafficMetricsDataRequest;
import com.zc.business.statistics.cache.*;
import com.zc.business.mapper.DcTrafficSectionDataMapper;
import com.zc.business.service.DcTrafficStatisticsService;
import com.zc.business.statistics.handler.TrafficAnalysis;
import com.zc.business.statistics.handler.TrafficStatistics;
import com.zc.business.utils.StakeMarkUtils;
import com.zc.common.core.httpclient.exception.HttpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* 交通断面数据服务实现类负责处理实时设备消息缓存数据定时任务以及数据保存等功能
*
* @author xiepufeng
*/
@Service
public class DcTrafficStatisticsServiceImpl
extends ServiceImpl<DcTrafficSectionDataMapper, DcTrafficSectionData>
implements DcTrafficStatisticsService {
// 日志记录器
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private RedisCache redisCache;
@Resource
private DcDeviceController dcDeviceController;
@Resource
private TrafficStatistics trafficStatistics;
@Resource
private TrafficAnalysis trafficAnalysis;
/**
* 初始化方法用于在对象创建后恢复各种周期的交通数据缓存
* 该方法标注了@PostConstruct注解确保在依赖注入完成后调用
*/
@PostConstruct
public void init() {
recoveryDailyCache(); // 从es中恢复当天交通数据缓存
recoveryMonthlyCache(); // 恢复每月交通数据缓存
recoveryQuarterlyCache(); // 恢复每季度交通数据缓存
recoveryYearlyCache(); // 恢复每年交通数据缓存
}
/**
* 处理实时接收到的一类交流站设备消息并将其转换为交通断面统计数据对象并缓存
*
* @param msg 设备发送的JSON格式实时消息
*/
@Override
public void processRealtimeOneStopMessage(JSONObject msg) {
// 1. 将设备消息转换为交通断面数据统计定义对象
List<DcTrafficSectionData> dcTrafficSectionDataList = trafficStatistics.convertToTrafficStatistics(msg, DeviceDataCategoryEnum.REAL_TIME);
if (dcTrafficSectionDataList != null && !dcTrafficSectionDataList.isEmpty()) {
// 2. 添加到缓存中
dcTrafficSectionDataList.forEach(this::addCacheData);
}
}
/**
* 根据请求获取当前交通流量指标数据
*
* @param request 包含获取流量指标所需的所有请求参数的对象
* @return 返回当前交通流量指标数据
* @throws ServiceException 如果没有获取到交通数据则抛出异常
*/
@Override
public DcTrafficMetricsData currentTrafficMetrics(DcTrafficMetricsDataRequest request) {
// 从Redis缓存中获取指定方向的交通路段数据列表
List<DcTrafficSectionData> dcTrafficSectionDataCaches = getDcTrafficSectionDataRedisCache(request.getDirection());
// 根据指定的方向和路段ID范围获取交通数据
List<DcTrafficSectionData> trafficSectionDataList = fetchTrafficDataByDirAndRange(request.getRoadSectionId(), dcTrafficSectionDataCaches);
// 对获取的交通路段数据进行分析,得到交通指标数据
List<DcTrafficMetricsData> dcTrafficMetricsDataList = trafficAnalysis.calculateTrafficMetrics(request, trafficSectionDataList);
if (dcTrafficMetricsDataList == null || dcTrafficMetricsDataList.isEmpty()) {
// 如果没有获取到数据,则抛出异常
throw new ServiceException("获取当前交通特征指数失败");
}
// 根据收集到的交通段数据计算并返回交通指标数据
return dcTrafficMetricsDataList.get(0);
}
/**
* 计算获取指定条件下的历史交通流量指标数据
*
* @param request 包含查询条件的请求对象包括开始时间结束时间方向周期类型和所属路段ID等信息
* @return 返回一个包含交通流量指标数据的列表
*/
@Override
public List<DcTrafficMetricsData> historyTrafficMetrics(DcTrafficMetricsDataRequest request) {
if (request.getStartTime() == null || request.getEndTime() == null) {
throw new ServiceException("开始时间或结束时间不能为空");
}
if (request.getPeriodType() == null) {
throw new ServiceException("时段类型不能为空");
}
// 构建查询条件
LambdaQueryWrapper<DcTrafficSectionData> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.between(DcTrafficSectionData::getStatisticalDate, request.getStartTime(), request.getEndTime());
queryWrapper.eq(DcTrafficSectionData::getPeriodType, request.getPeriodType());
queryWrapper.eq(DcTrafficSectionData::getDirection, request.getDirection());
// 根据请求获取所属路段ID,并进一步筛选路段范围内的数据
Long roadSectionId = request.getRoadSectionId();
if (roadSectionId != null) {
// 从缓存中获取路段信息,并根据路段的起止桩号筛选数据
DcRoadSection dcRoadSection = redisCache.getCacheMapValue(RedisKeyConstants.DC_ROAD_SECTION, roadSectionId);
if (dcRoadSection != null) {
queryWrapper.between(
DcTrafficSectionData::getStakeMark,
StakeMarkUtils.stakeMarkToInt(dcRoadSection.getStartStakeMark()),
StakeMarkUtils.stakeMarkToInt(dcRoadSection.getEndStakeMark()));
}
}
// 根据收集到的交通段数据计算并返回交通指标数据
return trafficAnalysis.calculateTrafficMetrics(request, list(queryWrapper));
}
/**
* 恢复每日缓存的函数
* 该方法尝试从物联平台获取所有设备信息并对这些信息进行处理
* 如果获取信息失败或处理过程中发生异常则记录错误信息
*/
private void recoveryDailyCache() {
try {
// 尝试从指定产品ID获取设备信息
Map<String, Object> oneStopDeviceMap = dcDeviceController.getDeviceByProductId(IotProductEnum.ONE_STOP_PRODUCT.value());
// 检查获取的设备信息是否为空
if (oneStopDeviceMap == null || oneStopDeviceMap.get("data") == null) {
logger.error("获取一类交通量调查站设备数据失败,产品id:{}", IotProductEnum.ONE_STOP_PRODUCT.value());
return;
}
// 将获取的设备信息转换为JSON数组,并遍历处理每个设备的数据
JSONArray deviceJsonArray = JSONArray.parseArray(oneStopDeviceMap.get("data").toString());
deviceJsonArray.forEach(trafficStatistics::processDeviceData);
} catch (HttpException | IOException e) {
// 记录处理设备数据时发生的异常
logger.error("处理设备数据时发生异常", e);
}
}
/**
* 恢复每月交通数据缓存的方法
* 通过查询当前月份至今的每日交通数据并将其添加到每月交通统计缓存中
*/
private void recoveryMonthlyCache() {
// 构建查询条件,查询当前月份至今的每日交通数据
LambdaQueryWrapper<DcTrafficSectionData> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DcTrafficSectionData::getPeriodType, TrafficDataPeriodTypeEnum.DAY);
queryWrapper.between(DcTrafficSectionData::getStatisticalDate, DateUtil.beginOfMonth(new Date()), new Date());
List<DcTrafficSectionData> dcTrafficSectionDataList = this.list(queryWrapper);
// 遍历查询结果,将每日数据添加到每月交通统计缓存
dcTrafficSectionDataList.forEach(MonthlyTrafficStatisticsCache::addCacheData);
}
/**
* 恢复每季度交通数据缓存的方法
* 通过查询当前季度至今的每月交通数据并将其添加到每季度交通统计缓存中
*/
private void recoveryQuarterlyCache() {
// 构建查询条件,查询当前季度至今的每月交通数据
LambdaQueryWrapper<DcTrafficSectionData> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DcTrafficSectionData::getPeriodType, TrafficDataPeriodTypeEnum.MONTH);
queryWrapper.between(DcTrafficSectionData::getStatisticalDate, DateUtil.beginOfQuarter(new Date()), new Date());
List<DcTrafficSectionData> dcTrafficSectionDataList = this.list(queryWrapper);
// 遍历查询结果,将每月数据添加到每季度交通统计缓存
dcTrafficSectionDataList.forEach(QuarterlyTrafficStatisticsCache::addCacheData);
}
/**
* 恢复每年交通数据缓存的方法
* 通过查询当前年份至今的每季度交通数据并将其添加到每年交通统计缓存中
*/
private void recoveryYearlyCache() {
// 构建查询条件,查询当前年份至今的每季度交通数据
LambdaQueryWrapper<DcTrafficSectionData> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DcTrafficSectionData::getPeriodType, TrafficDataPeriodTypeEnum.QUARTER);
queryWrapper.between(DcTrafficSectionData::getStatisticalDate, DateUtil.beginOfYear(new Date()), new Date());
List<DcTrafficSectionData> dcTrafficSectionDataList = this.list(queryWrapper);
// 遍历查询结果,将每季度数据添加到每年交通统计缓存
dcTrafficSectionDataList.forEach(YearlyTrafficStatisticsCache::addCacheData);
}
/**
* 将交通数据添加到缓存中
* 该方法将交通数据既添加到日交通数据缓存中也实时缓存到Redis中
*
* @param dcTrafficSectionData 交通段数据对象包含交通数据的详细信息
*/
private void addCacheData(DcTrafficSectionData dcTrafficSectionData) {
// 添加到日交通数据缓存中
DailyTrafficStatisticsCache.addCacheData(dcTrafficSectionData);
// 将数据缓存到redis中
redisCache.setCacheMapValue(RedisKeyConstants.getDcDevicesTrafficStatisticsKey(dcTrafficSectionData.getDirection()),
dcTrafficSectionData.getDeviceId(),
dcTrafficSectionData);
}
/**
* 从Redis缓存中获取指定方向的交通路段数据列表
*
* @param direction 交通方向如果为null则获取双向数据
* @return DcTrafficSectionData列表确保列表非空
*/
public List<DcTrafficSectionData> getDcTrafficSectionDataRedisCache(Byte direction) {
Map<String, DcTrafficSectionData> dcTrafficSectionDataMap = new HashMap<>();
// 根据方向选择相应的交通数据
Map<String, DcTrafficSectionData> trafficDataMap = (direction != null)
? getTrafficDataByDirection(direction)
: getTrafficDataForBothDirections();
// 将获取的交通数据合并到结果映射中
if (trafficDataMap != null) {
dcTrafficSectionDataMap.putAll(trafficDataMap);
}
// 将结果映射的值转换为列表并返回,确保列表非空
return new ArrayList<>(dcTrafficSectionDataMap.values());
}
/**
* 根据方向获取交通流量数据
*
* @param direction 方向
* @return 单向交通流量数据
*/
private Map<String, DcTrafficSectionData> getTrafficDataByDirection(Byte direction) {
return redisCache.getCacheMapValue(RedisKeyConstants.getDcDevicesTrafficStatisticsKey(direction));
}
/**
* 获取上行和下行所有交通流量数据
*
* @return 所有交通流量数据
*/
private Map<String, DcTrafficSectionData> getTrafficDataForBothDirections() {
Map<String, DcTrafficSectionData> allData = new HashMap<>();
allData.putAll(redisCache.getCacheMapValue(RedisKeyConstants.getDcDevicesTrafficStatisticsKey(LaneDirectionEnum.UPWARD.getValue())));
allData.putAll(redisCache.getCacheMapValue(RedisKeyConstants.getDcDevicesTrafficStatisticsKey(LaneDirectionEnum.DOWNWARD.getValue())));
return allData;
}
/**
* 根据指定的方向和路段ID范围获取交通数据
*
* @param roadSectionId 指定的路段ID如果为null则不根据路段筛选数据
* @return 返回符合指定方向和路段范围的交通段数据列表
*/
public List<DcTrafficSectionData> fetchTrafficDataByDirAndRange(Long roadSectionId, List<DcTrafficSectionData> dcTrafficSectionDataCaches) {
// 初始化路段起始和终止桩号
Integer startStakeMark;
Integer endStakeMark;
// 根据提供的路段ID查询路段信息,并转换为起始和终止桩号
if (roadSectionId != null) {
DcRoadSection dcRoadSection = redisCache.getCacheMapValue(RedisKeyConstants.DC_ROAD_SECTION, roadSectionId);
// 验证路段ID是否存在
if (dcRoadSection == null) {
throw new ServiceException("路段ID不存在");
}
startStakeMark = StakeMarkUtils.stakeMarkToInt(dcRoadSection.getStartStakeMark());
endStakeMark = StakeMarkUtils.stakeMarkToInt(dcRoadSection.getEndStakeMark());
} else {
startStakeMark = null;
endStakeMark = null;
}
// 筛选并返回符合路段范围条件的交通数据列表
return dcTrafficSectionDataCaches.stream()
.filter(data -> startStakeMark == null || endStakeMark == null
|| (data.getStakeMark() >= startStakeMark && data.getStakeMark() <= endStakeMark))
.collect(Collectors.toList());
}
}

19
zc-business/src/main/java/com/zc/business/service/impl/DcTrafficVolumeForecastServiceImpl.java

@ -0,0 +1,19 @@
package com.zc.business.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zc.business.domain.DcTrafficVolumeForecast;
import com.zc.business.mapper.DcTrafficVolumeForecastMapper;
import com.zc.business.service.DcTrafficVolumeForecastService;
import org.springframework.stereotype.Service;
/**
* 交通断面数据服务实现类负责处理实时设备消息缓存数据定时任务以及数据保存等功能
*
* @author xiepufeng
*/
@Service
public class DcTrafficVolumeForecastServiceImpl
extends ServiceImpl<DcTrafficVolumeForecastMapper, DcTrafficVolumeForecast>
implements DcTrafficVolumeForecastService {
}

79
zc-business/src/main/java/com/zc/business/statistics/handler/RealtimeTrafficStatistics.java

@ -1,79 +0,0 @@
package com.zc.business.statistics.handler;
import com.ruoyi.common.utils.DateUtils;
import com.zc.business.domain.DcTrafficSectionData;
import com.zc.business.enums.TrafficDataPeriodTypeEnum;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
public class RealtimeTrafficStatistics {
/**
* 对给定的交通数据集合进行统计分析返回一个综合交通数据对象
*
* @param dataCollection 交通数据集合不可为null或空包含多个交通路段的详细数据
* @param trafficDataPeriodType 交通数据的时段类型例如小时周等
* @return 综合交通数据对象包含车流量总和平均车速等统计结果如果输入数据为空则返回null
*/
public static DcTrafficSectionData trafficStatistics(Collection<DcTrafficSectionData> dataCollection, TrafficDataPeriodTypeEnum trafficDataPeriodType) {
// 判断输入数据是否为空
if (CollectionUtils.isEmpty(dataCollection)) {
return null;
}
// 创建一个汇总统计用的对象
DcTrafficSectionData aggregatedData = new DcTrafficSectionData();
// 初始化车流量总和
int trafficVolume = 0;
// 初始化最大车流量
int largeTrafficVolume = 0;
// 初始化计算平均车速所需的分子部分
double numerator = 0;
// 遍历原始数据列表,累加车流量并计算平均车速
for (DcTrafficSectionData data: dataCollection) {
// 累加车流量
trafficVolume += data.getTrafficVolume();
// 累加最大车流量
largeTrafficVolume += data.getLargeTrafficVolume();
// 计算分子部分
numerator += data.getAverageSpeed() * data.getTrafficVolume();
}
// 使用第一个数据项的信息填充汇总统计对象的基本属性(设备ID、桩号、方向)
DcTrafficSectionData firstDcTrafficSectionData = dataCollection.iterator().next();
// 设备id
aggregatedData.setDeviceId(firstDcTrafficSectionData.getDeviceId());
// 桩号
aggregatedData.setStakeMark(firstDcTrafficSectionData.getStakeMark());
// 道路方向
aggregatedData.setDirection(firstDcTrafficSectionData.getDirection());
// 上报时间
aggregatedData.setReportTime(firstDcTrafficSectionData.getReportTime());
// 计算平均车速并设置到汇总统计对象中
if (trafficVolume != 0) {
aggregatedData.setAverageSpeed((int) Math.round(numerator / trafficVolume));
} else {
// 若车流量为0,则默认设置平均车速为0
aggregatedData.setAverageSpeed(0);
}
// 时段类型
aggregatedData.setPeriodType(trafficDataPeriodType);
// 设置统计时间
aggregatedData.setStatisticalDate(firstDcTrafficSectionData.getStatisticalDate(), trafficDataPeriodType);
// 车流量
aggregatedData.setTrafficVolume(trafficVolume);
// 大型车车流量
aggregatedData.setLargeTrafficVolume(largeTrafficVolume);
// 更新或插入操作
aggregatedData.setUpdateTime(DateUtils.getNowDate());
// 生成主键
aggregatedData.generateUniqueId();
return aggregatedData;
}
}

635
zc-business/src/main/java/com/zc/business/statistics/handler/TrafficAnalysis.java

@ -0,0 +1,635 @@
package com.zc.business.statistics.handler;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.redis.RedisCache;
import com.zc.business.constant.RedisKeyConstants;
import com.zc.business.constant.StakeMarkConstant;
import com.zc.business.domain.DcRoadSection;
import com.zc.business.domain.DcTrafficMetricsData;
import com.zc.business.domain.DcTrafficSectionData;
import com.zc.business.domain.DcTrafficVolumeForecast;
import com.zc.business.enums.*;
import com.zc.business.request.DcTrafficMetricsDataRequest;
import com.zc.business.service.DcTrafficVolumeForecastService;
import com.zc.business.utils.AlgorithmUtils;
import com.zc.business.utils.StakeMarkUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
* 交通数据分析类
* 用于对交通数据进行各种分析处理
*
* @author xiepufeng
*/
@Component
public class TrafficAnalysis {
@Resource
private RedisCache redisCache;
@Resource
private DcTrafficVolumeForecastService dcTrafficVolumeForecastService;
// 使用ConcurrentHashMap来缓存路段ID和设计交通量,以提高并发性能和减少数据库访问
private static final Map<Long, HashMap<Integer, Integer>> designTrafficVolumeCache = new HashMap<>();
// 缓存路段列表
private static List<DcRoadSection> orderedDcRoadSectionCache;
/**
* 计算交通指标数据
* 根据提供的请求信息和路段数据列表计算相应的交通指标可以针对全程或指定路段进行统计统计结果受请求中的分段标志路段ID方向和周期类型影响
*
* @param request 包含交通指标计算请求信息的对象如是否分路段路段ID方向和周期类型
* @param trafficSectionDataList 包含多个路段数据的列表用于计算交通指标
* @return 返回一个交通指标数据列表每个列表项对应一个路段的交通指标数据
*/
public List<DcTrafficMetricsData> calculateTrafficMetrics(
DcTrafficMetricsDataRequest request,
List<DcTrafficSectionData> trafficSectionDataList) {
// 处理空列表的情况,如果列表为空,则直接返回一个空列表
if (trafficSectionDataList.isEmpty()) {
return Collections.emptyList();
}
// 根据请求信息决定如何计算交通指标
boolean segmented = request.isSegmented(); // 是否按分路段计算
Long roadSectionId = request.getRoadSectionId(); // 指定的路段ID
Byte direction = request.getDirection(); // 行驶方向
Byte periodType = request.getPeriodType(); // 周期类型
// 根据是否指定了路段ID,以及是否需要分路段统计来选择计算方法
if (roadSectionId == null) {
// 如果没有指定路段ID,则根据是否分路段来调用不同的计算方法
return segmented ? calculateFullSectionTrafficMetrics(trafficSectionDataList, direction, periodType) // 分路段统计
: calculateFullTrafficMetrics(trafficSectionDataList, direction, periodType); // 全程统计
} else {
// 如果指定了路段ID,则只统计该路段
return calculateSectionTrafficMetrics(trafficSectionDataList, roadSectionId, direction, periodType);
}
}
/**
* 计算整个路的路段的交通指标数据
*
* @param sectionDataList 路段交通数据列表包含各个监测点的交通数据
* @param direction 交通方向表示数据是朝哪个方向收集的
* @param periodType 时期类型标识数据是属于哪个时间段的
* @return 返回一个包含整个路段交通指标数据的列表
*/
private List<DcTrafficMetricsData> calculateFullSectionTrafficMetrics(
List<DcTrafficSectionData> sectionDataList,
Byte direction,
Byte periodType
) {
// 如果输入的路段交通数据列表为空或为空列表,直接返回空列表。
if (sectionDataList == null || sectionDataList.isEmpty()) {
return Collections.emptyList();
}
// 将输入的路段交通数据列表按照路段ID进行分组,得到一个Map,key为路段ID,value为该路段的交通数据列表。
Map<Long, List<DcTrafficSectionData>> sectionDataMap = groupTrafficSectionDataByRoadSectionId(sectionDataList);
// 统计每个路段的交通指标数据。
List<DcTrafficMetricsData> collectTrafficMetricsDataList = new ArrayList<>();
// 遍历整理后的路段交通数据,计算每个路段的交通指标。
sectionDataMap.forEach((roadSectionId, trafficSectionData) -> collectTrafficMetricsDataList.addAll(calculateSectionTrafficMetrics(trafficSectionData, roadSectionId, direction, periodType)));
// 返回计算得到的整个路段的交通指标数据列表。
return collectTrafficMetricsDataList;
}
/**
* 计算全程的交通指标数据
*
* @param trafficSectionDataList 交通路段数据列表包含各个路段的交通统计信息
* @param direction 行驶方向用于筛选数据
* @param periodType 统计周期类型用于区分不同类型的交通数据
* @return 返回一个包含完整交通指标数据的列表每个指标对应一个日期和行驶方向
*/
private List<DcTrafficMetricsData> calculateFullTrafficMetrics(
List<DcTrafficSectionData> trafficSectionDataList,
Byte direction,
Byte periodType
) {
// 检查输入数据是否为空,若为空则直接返回空列表
if (trafficSectionDataList == null || trafficSectionDataList.isEmpty()) {
return Collections.emptyList();
}
// 根据统计日期和行驶方向对交通数据进行分组
Map<Date, Map<Byte, List<DcTrafficSectionData>>> groupedByDateAndDirection = trafficSectionDataList.stream()
.collect(Collectors.groupingBy(
DcTrafficSectionData::getStatisticalDate,
Collectors.groupingBy(DcTrafficSectionData::getDirection)
));
List<DcTrafficMetricsData> result = new ArrayList<>();
// 遍历分组后的数据,计算每组数据的交通指标
groupedByDateAndDirection.forEach((date, dataMap) -> {
// 合并同一日期不同方向的所有数据
List<DcTrafficSectionData> sectionDataList = new ArrayList<>();
dataMap.forEach((key, value) -> sectionDataList.addAll(value));
// 根据合并后的数据计算交通指标
DcTrafficMetricsData metricsData = new DcTrafficMetricsData();
// 设置周期类型
metricsData.setPeriodType(periodType);
// 设置统计日期
metricsData.setStatisticalDate(date);
// 计算交通组成特征指数
metricsData.setTrafficFeature(calculateTrafficFeature(sectionDataList));
// 计算饱和度
metricsData.setSaturationLevel(calculateFullSaturationDegree(sectionDataList, direction, periodType));
// 计算通道拥挤度
metricsData.setChannelCongestionLevel(calculateSectionChannelCongestionLevel(sectionDataList));
// 计算路网整体拥堵的方法
DcTrafficMetricsData trafficMetricsData = calculateRoadNetworkCongestion(sectionDataList);
// 拥堵路段数量
metricsData.setCongestedSectionQuantity(trafficMetricsData.getCongestedSectionQuantity());
// 返回拥堵里程
metricsData.setCongestedDistance(trafficMetricsData.getCongestedDistance());
// 计算路网拥堵指数
metricsData.setRoadNetworkCongestionLevel(trafficMetricsData.getRoadNetworkCongestionLevel());
result.add(metricsData);
});
return result;
}
/**
* 计算路段交通指标的方法
*
* @param trafficSectionDataList 交通路段数据列表包含各个时间段的交通数据
* @param roadSectionId 路段ID用于指定需要计算交通指标的路段
* @param direction 方向标识用于指定计算交通指标的方向
* @param periodType 时段类型用于指定计算交通指标的时间段分类季度
* @return 返回一个交通指标数据列表每个指标包含特定日期和方向的交通组成特征值饱和度通道拥挤度和断面交通量
*/
private List<DcTrafficMetricsData> calculateSectionTrafficMetrics(
List<DcTrafficSectionData> trafficSectionDataList,
Long roadSectionId,
Byte direction,
Byte periodType
) {
// 检查输入数据列表是否为空,若为空则直接返回空列表
if (trafficSectionDataList == null || trafficSectionDataList.isEmpty()) {
return Collections.emptyList();
}
// 根据统计日期和行驶方向对交通数据进行分组
Map<Date, Map<Byte, List<DcTrafficSectionData>>> groupedByDateAndDirection = trafficSectionDataList.stream()
.collect(Collectors.groupingBy(
DcTrafficSectionData::getStatisticalDate,
Collectors.groupingBy(DcTrafficSectionData::getDirection)
));
List<DcTrafficMetricsData> result = new ArrayList<>();
// 遍历分组后的数据,计算每组的交通指标
groupedByDateAndDirection.forEach((date, dataMap) -> {
// 合并同一日期不同方向的所有数据
List<DcTrafficSectionData> sectionDataList = new ArrayList<>();
dataMap.forEach((key, value) -> sectionDataList.addAll(value));
// 根据合并后的数据计算交通指标
DcTrafficMetricsData metricsData = new DcTrafficMetricsData();
// 设置路段ID
metricsData.setRoadSectionId(roadSectionId);
// 时段类型
metricsData.setPeriodType(periodType);
// 设置方向
metricsData.setDirection(direction);
// 设置统计日期
metricsData.setStatisticalDate(date);
// 计算交通组成特征指数
metricsData.setTrafficFeature(calculateTrafficFeature(sectionDataList));
// 计算饱和度
metricsData.setSaturationLevel(calculateSectionSaturationDegree(sectionDataList, roadSectionId, direction, periodType, DateUtil.year(date)));
// 计算通道拥挤度
metricsData.setChannelCongestionLevel(calculateSectionChannelCongestionLevel(sectionDataList));
// 计算断面交通量
metricsData.setTrafficVolume(calculateSectionTrafficVolume(dataMap));
result.add(metricsData);
});
return result;
}
/**
* 计算交通组成特征值
*
* @param trafficSectionData 交通数据集合
* @return 大车流量在总车流量中占比的百分比如果数据集合为空或总流量为0则返回null
*/
private Integer calculateTrafficFeature(List<DcTrafficSectionData> trafficSectionData) {
// 检查数据集合是否为空,是则返回 0
if (trafficSectionData == null || trafficSectionData.isEmpty()) {
return null;
}
// 初始化总流量和大车流量总和
int totalTrafficVolume = 0;
int totalLargeTrafficVolume = 0;
// 使用Stream API计算大流量和总流量的累加值,同时处理了可能的NullPointerException
for (DcTrafficSectionData data : trafficSectionData) {
totalLargeTrafficVolume += data.getLargeTrafficVolume();
totalTrafficVolume += data.getTrafficVolume();
}
// 总流量为0时,返回0
if (totalTrafficVolume == 0) {
return null;
}
// 计算大流量占比的百分比并返回
double percentage = (double) totalLargeTrafficVolume / totalTrafficVolume * 100;
return (int) Math.round(percentage);
}
/**
* 计算整个路网的饱和度程度
*
* @param trafficSectionDataList 交通路段数据列表
* @param direction 行驶方向用字节表示
* @param periodType 时期类型用于确定是哪个时间段的交通数据季度
* @return 计算得到的整个路段平均饱和度如果输入数据为空则返回null
*/
private Integer calculateFullSaturationDegree(List<DcTrafficSectionData> trafficSectionDataList, Byte direction, Byte periodType) {
// 检查输入的数据映射是否为空,若为空则直接返回null
if (trafficSectionDataList == null || trafficSectionDataList.isEmpty()) {
return null;
}
// 将输入的路段交通数据列表按照路段ID进行分组,得到一个Map,key为路段ID,value为该路段的交通数据列表。
Map<Long, List<DcTrafficSectionData>> trafficSectionMap = groupTrafficSectionDataByRoadSectionId(trafficSectionDataList);
List<Integer> sectionSaturationDegreeList = new ArrayList<>();
// 遍历每个路段的数据列表,计算并收集每个路段的饱和度
trafficSectionMap.forEach((roadSectionId, sectionDataList) -> {
if (!sectionDataList.isEmpty()) {
Integer year = DateUtil.year(sectionDataList.get(0).getStatisticalDate());
Integer saturationDegree = calculateSectionSaturationDegree(sectionDataList, roadSectionId, direction, periodType, year);
sectionSaturationDegreeList.add(saturationDegree);
}
});
// 计算所有路段饱和度的平均值
double averageSaturationDegree = sectionSaturationDegreeList.stream()
.mapToDouble(Integer::doubleValue)
.average()
.orElse(0.0);
// 将平均饱和度值四舍五入为整数后返回
return (int) Math.round(averageSaturationDegree);
}
/**
* 计算路段饱和度
*
* @param trafficSectionData 交通路段数据列表
* @param roadSectionId 路段ID用于标识具体的路段
* @param direction 行驶方向用于确定交通量是否需要折半计算
* @param periodType 时期类型用于确定是哪个时间段的交通数据季度
* @param year 年份用于确定是哪年的交通数据
* @return 返回计算得到的路段饱和度饱和度为交通量与设计交通量的比值乘以100
*/
private Integer calculateSectionSaturationDegree(List<DcTrafficSectionData> trafficSectionData, Long roadSectionId, Byte direction, Byte periodType, Integer year) {
// 检查数据集合是否为空,是则返回 0
if (trafficSectionData == null || trafficSectionData.isEmpty()) {
return null;
}
// 获取设计交通量
Integer designTrafficVolume = getDesignTrafficVolume(roadSectionId, periodType, year);
// 如果指定了行驶方向,设计交通量需要折半计算
if (direction != null) {
designTrafficVolume = (int) Math.round(designTrafficVolume * 0.5);
}
// 获取当前交通量的平均值
double averageWeightedVolume = trafficSectionData.stream()
.mapToDouble(data ->
data.getLargeTrafficVolume() * VehicleLoadSaturationFactorEnum.LARGE_VEHICLE.getConversionFactor()
+ (data.getTrafficVolume() - data.getLargeTrafficVolume()))
.average()
.orElse(0.0);
// 计算饱和度
double saturationDegree = averageWeightedVolume / designTrafficVolume * 100;
return (int) Math.round(saturationDegree);
}
/**
* 计算路段通道拥挤度
*
* @param trafficSectionDataList 路段交通数据列表包含多个路段的交通情况数据
* @return 返回计算得到的路段通道拥挤度如果输入数据为空则返回0
*/
private Integer calculateSectionChannelCongestionLevel(List<DcTrafficSectionData> trafficSectionDataList) {
if (trafficSectionDataList == null || trafficSectionDataList.isEmpty()) {
return 0; // 列表为空时返回0
}
double totalWeightedSpeed = 0; // 总的加权速度
int totalVolume = 0; // 总的车流量,作为权重
// 遍历路段数据,计算加权速度和总车流量
for (DcTrafficSectionData data : trafficSectionDataList) {
if (data.getTrafficVolume() != null && data.getAverageSpeed() != null) {
totalWeightedSpeed += data.getTrafficVolume() * data.getAverageSpeed(); // 累加加权速度
totalVolume += data.getTrafficVolume(); // 累加权重
}
}
if (totalVolume == 0) {
return 0; // 防止除以0的情况
}
// 计算并返回加权平均速度
return (int) Math.round(totalWeightedSpeed / totalVolume);
}
/**
* 计算路网整体拥堵的方法
*
* @param trafficSectionDataList 交通路段数据列表包含各个路段的交通状况和里程信息
* @return DcTrafficMetricsData 包含路网整体拥堵指数拥堵路段数量和拥堵里程的数据对象
*/
public DcTrafficMetricsData calculateRoadNetworkCongestion(List<DcTrafficSectionData> trafficSectionDataList) {
DcTrafficMetricsData metricsData = new DcTrafficMetricsData();
// 如果输入的交通路段数据为空或为空列表,直接返回一个不含数据的metricsData对象
if (trafficSectionDataList == null || trafficSectionDataList.isEmpty()) {
return metricsData;
}
// 根据行驶方向对交通数据进行分组,以便分别计算每个方向的拥堵情况
Map<Byte, List<DcTrafficSectionData>> groupedByDirection = trafficSectionDataList.stream()
.collect(Collectors.groupingBy(DcTrafficSectionData::getDirection));
// 总里程,基于道路方向的数量乘以单方向的总里程
int totalDistance = StakeMarkConstant.calculateRoadLength() * groupedByDirection.size();
// 总拥堵里程,使用AtomicInteger以支持在并发环境中进行累加操作
AtomicInteger totalCongestionDistance = new AtomicInteger();
// 拥堵路段数量
AtomicInteger congestedSectionQuantity = new AtomicInteger();
// 遍历每个方向的数据,计算总拥堵里程
groupedByDirection.forEach((directionData, trafficSectionList) -> {
List<DcTrafficSectionData> sortedList;
// 根据行驶方向,对交通路段数据进行排序,上行方向逆序,下行方向正序
if (directionData.equals(LaneDirectionEnum.UPWARD.getValue())) {
sortedList = trafficSectionList.stream()
.sorted(Comparator.comparing(DcTrafficSectionData::getStakeMark).reversed())
.collect(Collectors.toList());
} else {
sortedList = trafficSectionList.stream()
.sorted(Comparator.comparing(DcTrafficSectionData::getStakeMark))
.collect(Collectors.toList());
}
// 用于计算拥堵里程的辅助变量
int previousStakeMark = 0;
int previousAverageSpeed = 0;
int defaultCongestionDistance = 0;
// 遍历排序后的路段数据,计算每个路段的拥堵里程,并累加到总拥堵里程中
for (DcTrafficSectionData dcTrafficSectionData : sortedList) {
int averageSpeed = dcTrafficSectionData.getAverageSpeed();
int stakeMark = dcTrafficSectionData.getStakeMark();
// 对于不拥堵的路段,累加之前计算的默认拥堵距离
if (!ChannelCongestionLevelEnum.isMediumOrSevereCongestion(averageSpeed)) {
totalCongestionDistance.addAndGet(defaultCongestionDistance);
previousStakeMark = stakeMark;
defaultCongestionDistance = 0;
continue;
}
// 根据平均速度计算默认拥堵距离
defaultCongestionDistance = ChannelCongestionLevelEnum.fromSpeed(averageSpeed).getDefaultCongestionDistance();
// 如果之前已经有路段被计算过,则根据两个路段之间的距离和默认拥堵距离,计算实际应累加的拥堵距离
if (previousAverageSpeed != 0) {
int congestionDistance = Math.abs(stakeMark - previousStakeMark);
if (congestionDistance > StakeMarkConstant.MAX_INTERVAL_MILLIMETER_WAVE_RADAR) {
totalCongestionDistance.addAndGet(defaultCongestionDistance);
} else {
totalCongestionDistance.addAndGet(congestionDistance);
congestedSectionQuantity.addAndGet(1);
}
}
// 更新辅助变量以备后续计算
previousStakeMark = stakeMark;
previousAverageSpeed = averageSpeed;
}
});
// 计算并返回路网整体的拥堵指数
metricsData.setRoadNetworkCongestionLevel(Math.round((float) totalCongestionDistance.get() / totalDistance * 100));
// 返回拥堵路段数量
metricsData.setCongestedSectionQuantity(congestedSectionQuantity.get());
// 返回拥堵里程
metricsData.setCongestedDistance(totalCongestionDistance.get());
return metricsData;
}
/**
* 计算路段交通量
*
* @param trafficSectionData 路段交通数据的映射key为方向value为该方向上的路段交通数据列表
* @return 返回计算出的总交通量如果输入数据为空则返回null
*/
private Integer calculateSectionTrafficVolume(Map<Byte, List<DcTrafficSectionData>> trafficSectionData) {
// 检查输入数据是否为空,为空则直接返回0
if (trafficSectionData == null || trafficSectionData.isEmpty()) {
return null;
}
// 使用AtomicReference来存放最终的交通量,确保线程安全
AtomicReference<Integer> trafficVolume = new AtomicReference<>(0);
// 遍历每个方向的路段数据,累加其交通量
trafficSectionData.forEach((direction, trafficSectionList) -> {
// 对每个方向上的路段数据,提取交通量并找出最大值,然后累加到总交通量上
trafficVolume.updateAndGet(v -> v + trafficSectionList.stream()
.map(DcTrafficSectionData::getTrafficVolume) // 提取每个路段数据的交通量
.max(Integer::compareTo).orElse(0)); // 找出最大交通量,若无则为0
});
// 返回最终计算出的总交通量
return trafficVolume.get();
}
/**
* 根据道路路段ID和周期类型年份获取设计交通量
*
* @param roadSectionId 道路路段的ID如果为null则默认为0
* @param periodType 交通数据的周期类型如果为null则默认计算五分钟内的交通量
* 周期类型包括年季度月以及其他默认情况
* @param year 年份用于查询指定年份的设计交通量
* @return 根据给定的路段ID和周期类型年份计算出的设计交通量
*/
private Integer getDesignTrafficVolume(Long roadSectionId, Byte periodType, Integer year) {
// 处理null的roadSectionId,默认为0
if (roadSectionId == null) {
roadSectionId = 0L;
}
Integer designTrafficVolume;
// 使用缓存来存储每年的设计交通量,减少数据库查询
HashMap<Integer, Integer> yearDesignTrafficVolumeCache = designTrafficVolumeCache.computeIfAbsent(roadSectionId, k -> new HashMap<>());
// 判断缓存中是否已存在当前年份的设计交通量,如果存在直接使用,否则进行查询
if (yearDesignTrafficVolumeCache.containsKey(year)) {
designTrafficVolume = yearDesignTrafficVolumeCache.get(year);
} else {
// 构建查询条件,并根据条件查询数据库获取设计交通量
LambdaQueryWrapper<DcTrafficVolumeForecast> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DcTrafficVolumeForecast::getRoadSectionId, roadSectionId);
queryWrapper.eq(DcTrafficVolumeForecast::getYear, year);
DcTrafficVolumeForecast dcTrafficVolumeForecast = dcTrafficVolumeForecastService.getOne(queryWrapper);
designTrafficVolume = dcTrafficVolumeForecast.getDesignTrafficVolume();
yearDesignTrafficVolumeCache.put(year, designTrafficVolume);
}
// 根据周期类型计算不同周期的交通量,默认为五分钟交通量
if (periodType == null) {
return Math.round(designTrafficVolume / ((float) 24 * 60 / 5));
}
TrafficDataPeriodTypeEnum typeEnum = TrafficDataPeriodTypeEnum.valueOfCode(periodType);
switch (typeEnum) {
case YEAR:
return designTrafficVolume * 365; // 年交通量
case QUARTER:
return designTrafficVolume * 91; // 季度交通量
case MONTH:
return designTrafficVolume * 31; // 月交通量
default:
return designTrafficVolume; // 默认为日交通量
}
}
/**
* 获取有序的DcRoadSection缓存列表
* 该方法首先检查有序的DcRoadSection缓存是否已经存在如果存在直接返回缓存如果缓存不存在则从Redis缓存中获取所有路段信息
* 根据路段的起止桩号进行排序然后将排序后的结果存储到有序的DcRoadSection缓存中并返回该缓存列表
*
* @return 返回一个按路段起止桩号顺序排序的DcRoadSection列表
*/
private List<DcRoadSection> getOrderedDcRoadSectionCache() {
if (orderedDcRoadSectionCache != null) {
return orderedDcRoadSectionCache; // 直接返回已缓存的有序路段信息
}
// 从Redis缓存中获取所有路段信息,并根据路段起止桩号进行排序
Map<String, DcRoadSection> dcRoadSectionMap = redisCache.getCacheMapValue(RedisKeyConstants.DC_ROAD_SECTION);
List<DcRoadSection> orderedDcRoadSection = dcRoadSectionMap.values().stream()
.sorted(Comparator.comparing((dcRoadSection) -> StakeMarkUtils.stakeMarkToInt(dcRoadSection.getEndStakeMark()) + StakeMarkUtils.stakeMarkToInt(dcRoadSection.getStartStakeMark())))
.collect(Collectors.toList());
orderedDcRoadSectionCache = orderedDcRoadSection; // 将排序后的路段信息缓存起来
return orderedDcRoadSection;
}
/**
* 使用二分查找在已排序的路段时间对象列表中找到对应的路段ID
*
* @param sortedRoadSectionObjects 已排序的路段时间对象列表列表中的对象应实现了Comparable接口
* @param dcTrafficSectionData 需要查找的交通数据段对象包含桩号信息
* @return 找到的路段ID如果没有找到则返回特定的标识-1
*/
private int findSectionIdByBinarySearch(List<Object> sortedRoadSectionObjects, DcTrafficSectionData dcTrafficSectionData) {
// 使用AlgorithmUtils的二分查找方法进行查找,比较函数通过lambda表达式定义
return AlgorithmUtils.binarySearch(sortedRoadSectionObjects, dcTrafficSectionData, (roadSection, trafficSectionData) -> {
DcRoadSection dcRoadSection = (DcRoadSection) roadSection;
Integer startStakeMark = StakeMarkUtils.stakeMarkToInt(dcRoadSection.getStartStakeMark());
Integer endStakeMark = StakeMarkUtils.stakeMarkToInt(dcRoadSection.getEndStakeMark());
DcTrafficSectionData trafficSectionData1 = (DcTrafficSectionData) trafficSectionData;
Integer stakeMark = trafficSectionData1.getStakeMark();
// 比较交通数据的桩号是否位于路段的起始桩号和结束桩号之间,以判断数据是否属于该路段
if (stakeMark >= startStakeMark && stakeMark <= endStakeMark) {
return 0; // 属于该路段,返回0表示找到
} else if (stakeMark < startStakeMark) {
return 1; // 桩号小于起始桩号,说明要查找的路段在当前路段的左侧
} else {
return -1; // 桩号大于结束桩号,说明要查找的路段在当前路段的右侧
}
});
}
/**
* 将交通数据按照路段ID分组
*
* @param trafficSectionDataList 交通路段数据列表每个数据包含路段相关信息
* @return 分组后的交通路段数据以路段ID为键对应路段的所有交通数据为值
*/
private Map<Long, List<DcTrafficSectionData>> groupTrafficSectionDataByRoadSectionId(List<DcTrafficSectionData> trafficSectionDataList) {
// 获取已排序的路段信息缓存。
List<DcRoadSection> sortedRoadSections = getOrderedDcRoadSectionCache();
// 创建一个对象列表,用于二分查找。
List<Object> sortedRoadSectionObjects = new ArrayList<>(sortedRoadSections.size());
sortedRoadSectionObjects.addAll(sortedRoadSections);
// 使用HashMap来组织路段交通数据,以便于后续处理。
Map<Long, List<DcTrafficSectionData>> sectionDataMap = new HashMap<>();
// 遍历输入的路段交通数据列表,将数据按照路段进行组织。
for (DcTrafficSectionData trafficSectionData : trafficSectionDataList) {
// 使用二分查找来找到当前交通数据所属的路段。
int index = findSectionIdByBinarySearch(sortedRoadSectionObjects, trafficSectionData);
// 如果找到对应的路段,则将交通数据添加到该路段的数据列表中。
if (index > -1) {
DcRoadSection dcRoadSection = sortedRoadSections.get(index);
List<DcTrafficSectionData> dcTrafficSectionList = sectionDataMap.putIfAbsent(dcRoadSection.getId(), new ArrayList<>());
dcTrafficSectionList.add(trafficSectionData);
}
}
return sectionDataMap;
}
}

257
zc-business/src/main/java/com/zc/business/service/impl/DcTrafficSectionDataServiceImpl.java → zc-business/src/main/java/com/zc/business/statistics/handler/TrafficStatistics.java

@ -1,42 +1,37 @@
package com.zc.business.service.impl;
package com.zc.business.statistics.handler;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.DateUtils;
import com.zc.business.constant.RedisKeyConstants;
import com.zc.business.constant.StatisticalRecoveryOffsetTime;
import com.zc.business.controller.DcDeviceController;
import com.zc.business.domain.DcDevice;
import com.zc.business.domain.DcTrafficSectionData;
import com.zc.business.enums.*;
import com.zc.business.statistics.cache.*;
import com.zc.business.mapper.DcTrafficSectionDataMapper;
import com.zc.business.service.DcTrafficSectionDataService;
import com.zc.business.statistics.handler.RealtimeTrafficStatistics;
import com.zc.business.statistics.cache.*;
import com.zc.common.core.httpclient.exception.HttpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.function.Consumer;
/**
* 交通断面数据服务实现类负责处理实时设备消息缓存数据定时任务以及数据保存等功能
* 交通数据统计处理类
*
* 该类提供了一系列方法用于对交通数据进行统计分析包括车流量总和平均车速等
* @author xiepufeng
*/
@Service
public class DcTrafficSectionDataServiceImpl
extends ServiceImpl<DcTrafficSectionDataMapper, DcTrafficSectionData>
implements DcTrafficSectionDataService {
@Component
public class TrafficStatistics {
// 日志记录器
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@ -50,35 +45,6 @@ public class DcTrafficSectionDataServiceImpl
@Resource
private DcDeviceController dcDeviceController;
/**
* 初始化方法用于在对象创建后恢复各种周期的交通数据缓存
* 该方法标注了@PostConstruct注解确保在依赖注入完成后调用
*/
@PostConstruct
public void init() {
recoveryDailyCache(); // 从es中恢复当天交通数据缓存
recoveryMonthlyCache(); // 恢复每月交通数据缓存
recoveryQuarterlyCache(); // 恢复每季度交通数据缓存
recoveryYearlyCache(); // 恢复每年交通数据缓存
}
/**
* 处理实时接收到的一类交流站设备消息并将其转换为交通断面统计数据对象并缓存
*
* @param msg 设备发送的JSON格式实时消息
*/
@Override
public void processRealtimeOneStopMessage(JSONObject msg) {
// 1. 将设备消息转换为交通断面数据统计定义对象
List<DcTrafficSectionData> dcTrafficSectionDataList = convertToTrafficStatistics(msg, DeviceDataCategory.REAL_TIME);
if (dcTrafficSectionDataList != null && !dcTrafficSectionDataList.isEmpty()) {
// 2. 将转换后的数据添加到缓存中
dcTrafficSectionDataList.forEach(DailyTrafficStatisticsCache::addCacheData);
}
}
/**
* 定义每小时第20分钟执行的任务用于清除过期缓存数据并将缓存中的数据整合后保存至数据库
*/
@ -103,32 +69,6 @@ public class DcTrafficSectionDataServiceImpl
persistAggregatedData(YearlyTrafficStatisticsCache.getCache(), TrafficDataPeriodTypeEnum.YEAR, (a) -> {});
}
/**
* 恢复每日缓存的函数
* 该方法尝试从物联平台获取所有设备信息并对这些信息进行处理
* 如果获取信息失败或处理过程中发生异常则记录错误信息
*/
private void recoveryDailyCache() {
try {
// 尝试从指定产品ID获取设备信息
Map<String, Object> oneStopDeviceMap = dcDeviceController.getDeviceByProductId(IotProductEnum.ONE_STOP_PRODUCT.value());
// 检查获取的设备信息是否为空
if (oneStopDeviceMap == null || oneStopDeviceMap.get("data") == null) {
logger.error("获取一类交通量调查站设备数据失败,产品id:{}", IotProductEnum.ONE_STOP_PRODUCT.value());
return;
}
// 将获取的设备信息转换为JSON数组,并遍历处理每个设备的数据
JSONArray deviceJsonArray = JSONArray.parseArray(oneStopDeviceMap.get("data").toString());
deviceJsonArray.forEach(this::processDeviceData);
} catch (HttpException | IOException e) {
// 记录处理设备数据时发生的异常
logger.error("处理设备数据时发生异常", e);
}
}
/**
* 处理设备数据的函数
@ -137,7 +77,7 @@ public class DcTrafficSectionDataServiceImpl
*
* @param deviceObject 设备对象需要是一个JSONObject包含设备信息
*/
private void processDeviceData(Object deviceObject) {
public void processDeviceData(Object deviceObject) {
JSONObject deviceJsonObject = (JSONObject)deviceObject;
// 提取设备ID
String deviceId = deviceJsonObject.getString("id");
@ -172,21 +112,12 @@ public class DcTrafficSectionDataServiceImpl
* @return HashMap<String, Object> 包含查询条件的HashMap对象
*/
private HashMap<String, Object> buildPropertiesRequiredParameter() {
// 获取数据统计的最大日期
Date maxStatisticalDate = dcTrafficSectionDataMapper.getMaxStatisticalDate();
// 如果最大统计日期为null,则计算默认的最大统计日期
if (maxStatisticalDate == null) {
maxStatisticalDate = DateUtil.offsetDay(new Date(), StatisticalRecoveryOffsetTime.TRAFFIC_SECTION_DATA_OFFSET_DAY);
}
HashMap<String, Object> props = new HashMap<>();
// 设置查询条件的键为“timestamp$BTW”,表示时间戳在一定范围内
props.put("terms[0].column", "timestamp$BTW");
ArrayList<String> dateList = new ArrayList<>();
// 添加当前日期的开始和结束时间到列表,用于设定时间范围
dateList.add(DateUtil.beginOfDay(maxStatisticalDate).toString());
dateList.add(DateUtil.beginOfMonth(new Date()).toString());
dateList.add(DateUtil.endOfDay(new Date()).toString());
// 将日期列表以逗号分隔并设置为查询条件的值
props.put("terms[0].value", String.join(",", dateList));
@ -224,7 +155,7 @@ public class DcTrafficSectionDataServiceImpl
JSONObject propertyJsonObject = (JSONObject)propertyObject;
// 将设备消息转换为交通断面数据统计定义对象
List<DcTrafficSectionData> dcTrafficSectionDataList = convertToTrafficStatistics(propertyJsonObject, DeviceDataCategory.HISTORY);
List<DcTrafficSectionData> dcTrafficSectionDataList = convertToTrafficStatistics(propertyJsonObject, DeviceDataCategoryEnum.HISTORY);
// 如果转换结果非空且不为空列表,则将转换后的数据添加到缓存中
if (dcTrafficSectionDataList != null && !dcTrafficSectionDataList.isEmpty()) {
@ -235,49 +166,6 @@ public class DcTrafficSectionDataServiceImpl
});
}
/**
* 恢复每月交通数据缓存的方法
* 通过查询当前月份至今的每日交通数据并将其添加到每月交通统计缓存中
*/
private void recoveryMonthlyCache() {
// 构建查询条件,查询当前月份至今的每日交通数据
LambdaQueryWrapper<DcTrafficSectionData> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DcTrafficSectionData::getPeriodType, TrafficDataPeriodTypeEnum.DAY);
queryWrapper.between(DcTrafficSectionData::getStatisticalDate, DateUtil.beginOfMonth(new Date()), new Date());
List<DcTrafficSectionData> dcTrafficSectionDataList = this.list(queryWrapper);
// 遍历查询结果,将每日数据添加到每月交通统计缓存
dcTrafficSectionDataList.forEach(MonthlyTrafficStatisticsCache::addCacheData);
}
/**
* 恢复每季度交通数据缓存的方法
* 通过查询当前季度至今的每月交通数据并将其添加到每季度交通统计缓存中
*/
private void recoveryQuarterlyCache() {
// 构建查询条件,查询当前季度至今的每月交通数据
LambdaQueryWrapper<DcTrafficSectionData> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DcTrafficSectionData::getPeriodType, TrafficDataPeriodTypeEnum.MONTH);
queryWrapper.between(DcTrafficSectionData::getStatisticalDate, DateUtil.beginOfQuarter(new Date()), new Date());
List<DcTrafficSectionData> dcTrafficSectionDataList = this.list(queryWrapper);
// 遍历查询结果,将每月数据添加到每季度交通统计缓存
dcTrafficSectionDataList.forEach(QuarterlyTrafficStatisticsCache::addCacheData);
}
/**
* 恢复每年交通数据缓存的方法
* 通过查询当前年份至今的每季度交通数据并将其添加到每年交通统计缓存中
*/
private void recoveryYearlyCache() {
// 构建查询条件,查询当前年份至今的每季度交通数据
LambdaQueryWrapper<DcTrafficSectionData> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DcTrafficSectionData::getPeriodType, TrafficDataPeriodTypeEnum.QUARTER);
queryWrapper.between(DcTrafficSectionData::getStatisticalDate, DateUtil.beginOfYear(new Date()), new Date());
List<DcTrafficSectionData> dcTrafficSectionDataList = this.list(queryWrapper);
// 遍历查询结果,将每季度数据添加到每年交通统计缓存
dcTrafficSectionDataList.forEach(YearlyTrafficStatisticsCache::addCacheData);
}
/**
* 将设备消息转换为交通断面数据统计定义对象
*
@ -286,11 +174,11 @@ public class DcTrafficSectionDataServiceImpl
* @return 转换后的交通断面数据统计定义对象
*/
public List<DcTrafficSectionData> convertToTrafficStatistics(JSONObject msg, DeviceDataCategory category) {
public List<DcTrafficSectionData> convertToTrafficStatistics(JSONObject msg, DeviceDataCategoryEnum category) {
// 获取属性
JSONObject property;
if (category == DeviceDataCategory.REAL_TIME) {
if (category == DeviceDataCategoryEnum.REAL_TIME) {
JSONObject properties = msg.getJSONObject("properties");
// 属性数据
@ -358,16 +246,16 @@ public class DcTrafficSectionDataServiceImpl
* @param reportTime 报告时间标识数据的时间戳
* @return 返回一个Map包含上行和下行的交通段数据
*/
private Map<LaneDirection, DcTrafficSectionData> processLaneData(JSONArray lanes, DcDevice dcDevice, Date reportTime) {
private Map<LaneDirectionEnum, DcTrafficSectionData> processLaneData(JSONArray lanes, DcDevice dcDevice, Date reportTime) {
Map<LaneDirection, DcTrafficSectionData> resultMap = new HashMap<>();
Map<LaneDirectionEnum, DcTrafficSectionData> resultMap = new HashMap<>();
// 初始化上行和下行的交通段数据
DcTrafficSectionData upwardData = new DcTrafficSectionData();
DcTrafficSectionData downwardData = new DcTrafficSectionData();
initializeTrafficSectionData(upwardData, dcDevice, reportTime, LaneDirection.UPWARD);
initializeTrafficSectionData(downwardData, dcDevice, reportTime, LaneDirection.DOWNWARD);
initializeTrafficSectionData(upwardData, dcDevice, reportTime, LaneDirectionEnum.UPWARD);
initializeTrafficSectionData(downwardData, dcDevice, reportTime, LaneDirectionEnum.DOWNWARD);
// 初始化上行和下行的车流量
int upwardTrafficVolume = 0, downwardTrafficVolume = 0;
@ -405,8 +293,8 @@ public class DcTrafficSectionDataServiceImpl
setTrafficSectionData(downwardData, downwardTrafficVolume, downwardCumulativeAverageSpeed, downwardLargeTrafficVolume);
// 将上行和下行的交通段数据放入结果映射中
resultMap.put(LaneDirection.UPWARD, upwardData);
resultMap.put(LaneDirection.DOWNWARD, downwardData);
resultMap.put(LaneDirectionEnum.UPWARD, upwardData);
resultMap.put(LaneDirectionEnum.DOWNWARD, downwardData);
return resultMap;
}
@ -419,7 +307,7 @@ public class DcTrafficSectionDataServiceImpl
* @param reportTime 数据上报时间
* @param direction 车道方向
*/
private void initializeTrafficSectionData(DcTrafficSectionData data, DcDevice dcDevice, Date reportTime, LaneDirection direction) {
private void initializeTrafficSectionData(DcTrafficSectionData data, DcDevice dcDevice, Date reportTime, LaneDirectionEnum direction) {
// 设置设备id
data.setDeviceId(dcDevice.getId());
@ -464,23 +352,23 @@ public class DcTrafficSectionDataServiceImpl
private int calculateCumulativeAverageSpeed(JSONObject laneData) {
// 根据laneData中的数据计算累计平均速度
// 累加平均速度(中小客车)
return laneData.getInteger("trafficNumberOfInAndSmall") * laneData.getInteger("inAndSmallAverageVehicleSpeed") +
// 累加平均速度(小型货车)
laneData.getInteger("trafficVolumeOfSmallTrucks") * laneData.getInteger("smallTrucksAverageVehicleSpeed") +
// 累加平均速度(大客车)
laneData.getInteger("busTrafficVolume") * laneData.getInteger("averageSpeedOfBus") +
// 累加平均速度(中型货车)
laneData.getInteger("mediumTruckTrafficVolume") * laneData.getInteger("averageSpeedOfMediumSizeTrucks") +
// 累加平均速度(大型货车)
laneData.getInteger("largeTruckTrafficVolume") * laneData.getInteger("averageSpeedOfLargeTrucks") +
// 累加平均速度(特大型货车)
laneData.getInteger("extraLargeTrucksTrafficVolume") * laneData.getInteger("averageSpeedOfExtraLargeTrucks") +
// 累加平均速度(集装箱车)
laneData.getInteger("containerTruckTrafficVolume") * laneData.getInteger("averageSpeedOfContainerTruck") +
// 累加平均速度(拖拉机)
laneData.getInteger("tractorTrafficVolume") * laneData.getInteger("averageSpeedOfTractor") +
// 累加平均速度(摩托车)
laneData.getInteger("motorcycleTrafficVolume") * laneData.getInteger("averageSpeedOfMotorcycle");
return laneData.getInteger("trafficNumberOfInAndSmall") * laneData.getInteger("inAndSmallAverageVehicleSpeed") +
// 累加平均速度(小型货车)
laneData.getInteger("trafficVolumeOfSmallTrucks") * laneData.getInteger("smallTrucksAverageVehicleSpeed") +
// 累加平均速度(大客车)
laneData.getInteger("busTrafficVolume") * laneData.getInteger("averageSpeedOfBus") +
// 累加平均速度(中型货车)
laneData.getInteger("mediumTruckTrafficVolume") * laneData.getInteger("averageSpeedOfMediumSizeTrucks") +
// 累加平均速度(大型货车)
laneData.getInteger("largeTruckTrafficVolume") * laneData.getInteger("averageSpeedOfLargeTrucks") +
// 累加平均速度(特大型货车)
laneData.getInteger("extraLargeTrucksTrafficVolume") * laneData.getInteger("averageSpeedOfExtraLargeTrucks") +
// 累加平均速度(集装箱车)
laneData.getInteger("containerTruckTrafficVolume") * laneData.getInteger("averageSpeedOfContainerTruck") +
// 累加平均速度(拖拉机)
laneData.getInteger("tractorTrafficVolume") * laneData.getInteger("averageSpeedOfTractor") +
// 累加平均速度(摩托车)
laneData.getInteger("motorcycleTrafficVolume") * laneData.getInteger("averageSpeedOfMotorcycle");
}
/**
@ -517,7 +405,7 @@ public class DcTrafficSectionDataServiceImpl
if (data.isStored()) {
continue;
}
DcTrafficSectionData aggregatedData = RealtimeTrafficStatistics.trafficStatistics(data.getData(), periodType);
DcTrafficSectionData aggregatedData = trafficStatistics(data.getData(), periodType);
if (dcTrafficSectionDataMapper.insertOrUpdate(aggregatedData)) {
// 设置数据已存储状态
data.setStored(true);
@ -527,4 +415,71 @@ public class DcTrafficSectionDataServiceImpl
}
}
/**
* 对给定的交通数据集合进行统计分析返回一个综合交通数据对象
*
* @param dataCollection 交通数据集合不可为null或空包含多个交通路段的详细数据
* @param trafficDataPeriodType 交通数据的时段类型例如小时周等
* @return 综合交通数据对象包含车流量总和平均车速等统计结果如果输入数据为空则返回null
*/
public DcTrafficSectionData trafficStatistics(Collection<DcTrafficSectionData> dataCollection, TrafficDataPeriodTypeEnum trafficDataPeriodType) {
// 判断输入数据是否为空
if (CollectionUtils.isEmpty(dataCollection)) {
return null;
}
// 创建一个汇总统计用的对象
DcTrafficSectionData aggregatedData = new DcTrafficSectionData();
// 初始化车流量总和
int trafficVolume = 0;
// 初始化最大车流量
int largeTrafficVolume = 0;
// 初始化计算平均车速所需的分子部分
double numerator = 0;
// 遍历原始数据列表,累加车流量并计算平均车速
for (DcTrafficSectionData data: dataCollection) {
// 累加车流量
trafficVolume += data.getTrafficVolume();
// 累加最大车流量
largeTrafficVolume += data.getLargeTrafficVolume();
// 计算分子部分
numerator += data.getAverageSpeed() * data.getTrafficVolume();
}
// 使用第一个数据项的信息填充汇总统计对象的基本属性(设备ID、桩号、方向)
DcTrafficSectionData firstDcTrafficSectionData = dataCollection.iterator().next();
// 设备id
aggregatedData.setDeviceId(firstDcTrafficSectionData.getDeviceId());
// 桩号
aggregatedData.setStakeMark(firstDcTrafficSectionData.getStakeMark());
// 道路方向
aggregatedData.setDirection(firstDcTrafficSectionData.getDirection());
// 上报时间
aggregatedData.setReportTime(firstDcTrafficSectionData.getReportTime());
// 计算平均车速并设置到汇总统计对象中
if (trafficVolume != 0) {
aggregatedData.setAverageSpeed((int) Math.round(numerator / trafficVolume));
} else {
// 若车流量为0,则默认设置平均车速为0
aggregatedData.setAverageSpeed(0);
}
// 时段类型
aggregatedData.setPeriodType(trafficDataPeriodType);
// 设置统计时间
aggregatedData.setStatisticalDate(firstDcTrafficSectionData.getStatisticalDate(), trafficDataPeriodType);
// 车流量
aggregatedData.setTrafficVolume(trafficVolume);
// 大型车车流量
aggregatedData.setLargeTrafficVolume(largeTrafficVolume);
// 更新或插入操作
aggregatedData.setUpdateTime(DateUtils.getNowDate());
// 生成主键
aggregatedData.generateUniqueId();
return aggregatedData;
}
}

54
zc-business/src/main/java/com/zc/business/utils/AlgorithmUtils.java

@ -0,0 +1,54 @@
package com.zc.business.utils;
import java.util.Comparator;
import java.util.List;
/**
* @author xiepufeng
* AlgorithmUtils 类提供了一系列的算法工具方法
* 主要用于各种算法的实现和操作
*/
public class AlgorithmUtils {
/**
* 在已排序的列表中使用二分查找算法来查找指定目标元素的索引
* @param list 一个有序的列表列表元素必须实现Comparable接口
* @param target 要在列表中查找的目标元素
* @return 如果目标元素在列表中找到则返回其索引如果未找到则返回-1
* @param <T> 列表和目标元素的类型该类型必须实现Comparable接口以支持比较操作
*/
public static <T> int binarySearch(List<T> list, T target, Comparator<T> comparator) {
// 检查输入列表是否为null
if (list == null) {
throw new IllegalArgumentException("输入列表不能为空.");
}
// 检查列表是否为空,如果是,直接返回-1
if (list.isEmpty()) {
return -1;
}
// 检查Comparator是否存在,如果没有则默认使用Comparable接口
Comparator<T> effectiveComparator = comparator != null ? comparator : (x, y) -> ((Comparable<T>) x).compareTo(y);
int low = 0; // 定义搜索范围的最低索引
int high = list.size() - 1; // 定义搜索范围的最高索引
while (low <= high) { // 当搜索范围未缩小到零时继续循环
int mid = low + (high - low) / 2; // 计算当前搜索范围的中间索引
int cmp = effectiveComparator.compare(list.get(mid), target); // 比较中间元素和目标元素
if (cmp < 0) { // 如果中间元素小于目标元素,则目标元素可能在右侧
low = mid + 1;
} else if (cmp > 0) { // 如果中间元素大于目标元素,则目标元素可能在左侧
high = mid - 1;
} else {
return mid; // 如果中间元素等于目标元素,则找到目标,返回其索引
}
}
return -1; // 如果未找到目标元素,则返回-1
}
}

369
zc-business/src/main/java/com/zc/business/utils/PoiUtil.java

@ -0,0 +1,369 @@
package com.zc.business.utils;
import com.aliyuncs.utils.IOUtils;
import com.google.common.base.Strings;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xddf.usermodel.chart.XDDFChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTPlotArea;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.util.StringUtils;
import java.io.*;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author : LCheng
* @date : 2020-12-10 10:03
* description : poi工具
*/
public class PoiUtil {
public static int headingCount1 = 1;
public static int headingCount2 = 1;
/**
* 根据word模板导出 针对图表柱状图折线图饼图等的处理
*
* @param docChart 图表对象
* @param title 图表标题
* @param seriesNames 系列名称数组
* @return {@link XWPFChart}
* @author LCheng
* @date 2020/12/10 11:08
*/
public static XWPFChart wordExportChar(XWPFChart docChart, String title, String[] seriesNames, XSSFSheet sheet) {
//获取图表数据对象
XDDFChartData chartData = docChart.getChartSeries().get(0);
//word图表均对应一个内置的excel,用于保存图表对应的数据
//excel中 第一列第二行开始的数据为分类信息
//CellRangeAddress(1, categories.size(), 0, 0) 四个参数依次为 起始行 截止行 起始列 截止列。
//根据分类信息的范围创建分类信息的数据源
XDDFDataSource catDataSource = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(1,sheet.getLastRowNum(),0,0));
//更新数据
for (int i = 0; i < seriesNames.length; i++) {
//excel中各系列对应的数据的范围
//根据数据的范围创建值的数据源
XDDFNumericalDataSource<Double> valDataSource = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1,sheet.getLastRowNum(),i+1,i+1));
//获取图表系列的数据对象
XDDFChartData.Series series = chartData.getSeries().get(i);
//替换系列数据对象中的分类和值
series.replaceData(catDataSource, valDataSource);
//修改系列数据对象中的标题
CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
series.setTitle(seriesNames[i], cellReference);
}
//更新图表数据对象
docChart.plot(chartData);
//图表整体的标题 传空值则不替换标题
if (!Strings.isNullOrEmpty(title)) {
docChart.setTitleText(title);
docChart.setTitleOverlay(false);
}
return docChart;
}
/**
* 合并docx文件
* @param srcDocxs 需要合并的目标docx文件
* @param destDocx 合并后的docx输出文件
*/
public static void mergeDoc(XWPFDocument srcDocxs, XWPFDocument destDocx) {
try {
//获取目标文件的CTDocument1对象
CTDocument1 ctDocument1 = srcDocxs.getDocument();
//获取第一个目标文件的CTBody对象
CTBody src1Body = ctDocument1.getBody();
//获取目标文件中的图表
List<XWPFChart> relations = srcDocxs.getCharts();
//判断是否有图表,没有图表的话,追加到之前的目标文件后面
if (relations.size() <= 0) {
CTBody src2Body = srcDocxs.getDocument().getBody();
//获取目标文件中的图片
List<XWPFPictureData> allPictures = srcDocxs.getAllPictures();
// 记录图片合并前及合并后的ID
Map<String,String> map = new HashMap();
//遍历图片
for (XWPFPictureData picture : allPictures) {
String before = srcDocxs.getRelationId(picture);
//将原文档中的图片加入到目标文档中
String after = destDocx.addPictureData(picture.getData(), Document.PICTURE_TYPE_PNG);
map.put(before, after);
}
//将当前文件的内容追加到之前的目标文件中
appendBody(src1Body, src2Body,map);
}
//遍历图表,
for (XWPFChart chart1 : relations) {
//是否是word中自带图表
if (chart1 instanceof XWPFChart) { // 如果是图表元素
XWPFChart chart = destDocx.createChart(5774310, 3076575);
CTPlotArea plotArea = chart1.getCTChart().getPlotArea();
chart.getCTChart().setPlotArea(plotArea);
chart.getCTChart().setLegend(chart1.getCTChart().getLegend());
}
}
//关闭流
srcDocxs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 合并chart
* @param chart 需要合并的目标chart
* @param destDocx 合并后的docx输出文件
*/
public static void mergeChart(XWPFChart chart, XWPFDocument destDocx) {
try {
XWPFChart docxChart = destDocx.createChart(5774310, 3076575);
CTPlotArea plotArea = chart.getCTChart().getPlotArea();
docxChart.getCTChart().setPlotArea(plotArea);
docxChart.getCTChart().setLegend(chart.getCTChart().getLegend());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 插入换行符
* @param destDocx 合并后的docx输出文件
*/
public static void createLineBreak(XWPFDocument destDocx) {
try {
XWPFParagraph paragraph = destDocx.createParagraph();
XWPFRun run = paragraph.createRun();
run.addBreak();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 合并文档内容
*
* @param src 目标文档
* @param append 要合并的文档
* @throws Exception
*/
private static void appendBody(CTBody src, CTBody append,Map<String,String> map) throws Exception {
XmlOptions optionsOuter = new XmlOptions();
optionsOuter.setSaveOuter();
//获取目标文件的字符内容
String srcString = src.xmlText();
//获取目标文件字符的开头
String prefix = srcString.substring(0, srcString.indexOf(">") + 1);
//获取目标文件字符的内容
String mainPart = srcString.substring(srcString.indexOf(">") + 1,
srcString.lastIndexOf("<"));
//获取目标文件字符的结尾
String sufix = srcString.substring(srcString.lastIndexOf("<"));
//获取需要追加的文件
String appendString = append.xmlText(optionsOuter);
//获取需要追加的文件内容(除去头和尾)
String addPart = appendString.substring(appendString.indexOf(">") + 1,
appendString.lastIndexOf("<"));
if (map != null && !map.isEmpty()) {
//对xml字符串中图片ID进行替换
for (Map.Entry<String, String> set : map.entrySet()) {
addPart = addPart.replace(set.getKey(), set.getValue());
}
}
//将获取到的文件内容合并成为新的CTBody
CTBody makeBody = CTBody.Factory.parse(prefix + mainPart + addPart
+ sufix);
//将新的CTBody重新设置到目标文件中
src.set(makeBody);
}
public static XWPFParagraph createHeading(XWPFDocument doc, String title) {
//段落
XWPFParagraph paragraph = doc.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText(title);
// run.setColor("696969");
run.setFontSize(18);
run.setBold(true);//标题加粗
return paragraph;
}
/**
* 创建标题1
*
* @param doc
* @param title
*/
public static void createHeading1(XWPFDocument doc, String title) {
//段落
XWPFParagraph paragraph = doc.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText(title);
// run.setColor("696969");
run.setFontSize(16);
run.setBold(true);//标题加粗
paragraph.setStyle("Heading1");
}
/**
* 创建标题2
*
* @param doc
* @param title
*/
public static void createHeading2(XWPFDocument doc, String title) {
XWPFParagraph paragraph = doc.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText(title);
run.setFontSize(14);
run.setBold(true);//标题加粗
paragraph.setStyle("Heading2");
}
public static void createTable(XWPFDocument doc) {
XWPFTable table = doc.createTable(3, 3);
//列宽自动分割
CTTblWidth infoTableWidth = table.getCTTbl().addNewTblPr().addNewTblW();
infoTableWidth.setType(STTblWidth.DXA);
infoTableWidth.setW(BigInteger.valueOf(9072));
setTableFonts(table.getRow(0).getCell(0), "编号");
setTableFonts(table.getRow(0).getCell(1), "问题");
setTableFonts(table.getRow(0).getCell(2), "应答");
setTableFonts(table.getRow(1).getCell(0), "1");
setTableFonts(table.getRow(1).getCell(1), "陈述日期");
setTableFonts(table.getRow(1).getCell(2), "2017年02月17日");
setTableFonts(table.getRow(2).getCell(0), "2");
setTableFonts(table.getRow(2).getCell(1), "PICS序列号");
setTableFonts(table.getRow(2).getCell(2), "121313132131");
}
// word跨列合并单元格
public static void mergeCellsHorizontal(XWPFTable table, int row, int fromCell, int toCell) {
for (int cellIndex = fromCell; cellIndex <= toCell; cellIndex++) {
XWPFTableCell cell = table.getRow(row).getCell(cellIndex);
if (cellIndex == fromCell) {
// The first merged cell is set with RESTART merge value
cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
} else {
// Cells which join (merge) the first one, are set with CONTINUE
cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
}
}
}
// word跨行并单元格
public static void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {
for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
if (rowIndex == fromRow) {
// The first merged cell is set with RESTART merge value
cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
} else {
// Cells which join (merge) the first one, are set with CONTINUE
cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
}
}
}
/**
* 设置表格中字体
*
* @param cell
* @param cellText
*/
public static void setTableFonts(XWPFTableCell cell, String cellText) {
CTP ctp = CTP.Factory.newInstance();
XWPFParagraph p = new XWPFParagraph(ctp, cell);
p.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = p.createRun();
run.setFontSize(8);
run.setText(cellText);
CTRPr rpr = run.getCTR().isSetRPr() ? run.getCTR().getRPr() : run.getCTR().addNewRPr();
CTFonts fonts = rpr.isSetRFonts() ? rpr.getRFonts() : rpr.addNewRFonts();
fonts.setAscii("仿宋");
fonts.setEastAsia("仿宋");
fonts.setHAnsi("仿宋");
cell.setParagraph(p);
}
/**
* 添加描述信息
*
* @param doc
* @param description
*/
public static void addDescription(XWPFDocument doc, String description) {
if (StringUtils.isEmpty(description)) {
return;
}
XWPFParagraph title = doc.createParagraph();
XWPFRun run = title.createRun();
run.setText(description);
run.setBold(true);
title.setAlignment(ParagraphAlignment.CENTER);
}
/**
* 创建目录
* 创建并插入带超链接的目录
* @param document
*/
public static void insertTOC2(XWPFDocument document) {
// 定义 TOC 字段属性
CTSimpleField tocField = CTSimpleField.Factory.newInstance();
tocField.setInstr("TOC \\h \\z \\t \"Heading1,Heading2\""); // 包含 Heading1 和 Heading2 样式的目录
// 创建包含 TOC 字段的段落
XWPFParagraph tocPara = document.createParagraph();
tocPara.getCTP().addNewFldSimple().set(tocField);
// 更新文档字段以计算目录
document.enforceUpdateFields();
}
/**
* 创建目录
* 创建并插入带超链接的目录
* @param document
*/
public static void insertTOC(XWPFDocument document) {
// 创建目录所在的段落
XWPFParagraph tocPara = document.createParagraph();
// 添加 TOC 域代码
String tocFieldCode = "TOC \\o \"1-3\" \\h \\z \\u";
CTSimpleField tocField = tocPara.getCTP().addNewFldSimple();
tocField.setInstr(tocFieldCode);
tocField.setDirty(STOnOff.TRUE);
}
}

37
zc-business/src/main/java/com/zc/business/utils/StakeMarkUtils.java

@ -0,0 +1,37 @@
package com.zc.business.utils;
/**
* StakeMarkUtils 桩号工具类
* @author xiepufeng
*/
public class StakeMarkUtils {
/**
* 将桩号格式的字符串转换为总米数
* 桩号格式字符串的样式可以是 "Xkm+Ym"其中 X 表示公里数Y 表示米数
* 公里数和米数之间使用 '+' 连接且公里数和米数的单位字符 'k' '+' 可以是任意大小写
*
* @param stakeMark 桩号格式的字符串例如 "1km+200m"
* @return 如果输入字符串为 null则返回 null否则返回计算得到的总米数
*/
public static Integer stakeMarkToInt(String stakeMark) {
if (stakeMark == null) {
return null;
}
// 使用正则表达式分割字符串,以不区分大小写的 'k' 和 '+' 为分隔符
String[] parts = stakeMark.split("(?i)k|\\+");
// 提取公里数和米数字符串
String kmStr = parts[1].trim();
int km = Integer.parseInt(kmStr); // 将公里数字符串转换为整数
int m = 0;
if (parts.length == 3) {
// 如果存在米数,则提取并转换为整数
String mStr = parts[2].trim();
m = Integer.parseInt(mStr);
}
// 计算并返回总米数
return km * 1000 + m;
}
}

229
zc-business/src/main/resources/mapper/business/DcTrafficIncidentsMapper.xml

@ -398,4 +398,233 @@
group by event_subclass) t3 on t1.eventSubclass = t3.event_subclass
</select>
<select id="selectTrafficControlAnalysis" resultType="java.util.Map">
select t1.controlCause,t1.controlCauseName,IFNULL(t2.facilityClose,0) facilityClose,
IFNULL(t2.facilityRestriction,0) facilityRestriction,IFNULL(t2.facilityInterval,0) facilityInterval
from
(select '1' controlCause,'车流量大' controlCauseName
union all select '2' controlCause,'交通事故' controlCauseName
union all select '3' controlCause,'恶劣天气' controlCauseName
union all select '4' controlCause,'施工' controlCauseName
union all select '5' controlCause,'警备任务' controlCauseName
union all select '6' controlCause,'其他' controlCauseName
) t1
left join
(select
t2.control_cause,
SUM(case when t2.classify = '6' then 1 else 0 end) facilityClose,
SUM(case when t2.classify = '7' then 1 else 0 end) facilityRestriction,
SUM(case when t2.classify = '9' then 1 else 0 end) facilityInterval
from dc_event t1
LEFT JOIN dc_event_traffic_control t2 on t1.id = t2.id
where
t1.event_type = '3'
and date_format(t1.start_time,'%Y-%m-%d %H:%i:%s') &lt;= date_format(#{endTime},'%Y-%m-%d %H:%i:%s')
and (date_format(t1.end_time,'%Y-%m-%d %H:%i:%s') > date_format(#{startTime},'%Y-%m-%d %H:%i:%s') or ISNULL(t1.end_time))
and t2.classify in ('6','7','9')
GROUP BY t2.control_cause) t2
on t1.controlCause = t2.control_cause
</select>
<select id="selectTrafficAccidentAnalysis" resultType="java.util.Map">
select t1.eventStateLabel,IFNULL(t2.num,0) num
from
(select '0' eventState,'待确认' eventStateLabel
union all select '1' eventState,'处理中' eventStateLabel
union all select '2' eventState,'已完成' eventStateLabel) t1
left join
(select event_state,count(*) num from dc_event
where event_type = '1' and date_format(start_time,'%y%m') = date_format(now(),'%y%m')
group by event_state) t2
on t1.eventState = t2.event_state
</select>
<select id="selectTrafficControlReasons" resultType="java.util.Map">
select t3.control_cause controlCause,t3.cause_type causeType,controlCauseName,causeTypeName,IFNULL(t4.num,0) num
from
(select 1 control_cause,'' cause_type,'车流量大' controlCauseName,'车流量大' causeTypeName
UNION ALL select 2 control_cause,'' cause_type,'交通事故' controlCauseName,'交通事故' causeTypeName
UNION ALL select 3 control_cause,'3-1' cause_type,'恶劣天气' controlCauseName,'雨' causeTypeName
UNION ALL select 3 control_cause,'3-2' cause_type,'恶劣天气' controlCauseName,'雪' causeTypeName
UNION ALL select 3 control_cause,'3-3' cause_type,'恶劣天气' controlCauseName,'雾' causeTypeName
UNION ALL select 3 control_cause,'3-4' cause_type,'恶劣天气' controlCauseName,'道路积水' causeTypeName
UNION ALL select 3 control_cause,'3-5' cause_type,'恶劣天气' controlCauseName,'道路湿滑' causeTypeName
UNION ALL select 3 control_cause,'3-6' cause_type,'恶劣天气' controlCauseName,'道路结冰' causeTypeName
UNION ALL select 3 control_cause,'3-7' cause_type,'恶劣天气' controlCauseName,'沙尘暴' causeTypeName
UNION ALL select 4 control_cause,'' cause_type,'施工' controlCauseName,'施工' causeTypeName
UNION ALL select 5 control_cause,'' cause_type,'警备任务' controlCauseName,'警备任务' causeTypeName
UNION ALL select 6 control_cause,'' cause_type,'其他' controlCauseName,'其他' causeTypeName) t3
LEFT JOIN
(select t2.control_type,t2.control_cause,t2.cause_type,count(*) num
from dc_event t1 LEFT JOIN dc_event_traffic_control t2 on t1.id = t2.id
where
t1.event_type = '3' and t1.event_subclass = '3-2' and t2.control_type = #{controlType}
and date_format(t1.start_time,'%Y-%m-%d %H:%i:%s') &lt;= date_format(#{endTime},'%Y-%m-%d %H:%i:%s')
and (date_format(t1.end_time,'%Y-%m-%d %H:%i:%s') > date_format(#{startTime},'%Y-%m-%d %H:%i:%s') or ISNULL(t1.end_time))
GROUP BY t2.control_cause,t2.cause_type) t4
on t3.control_cause = t4.control_cause and t3.cause_type = t4.cause_type
ORDER BY t3.control_cause, t3.cause_type
</select>
<select id="selectClosedQuantity" resultType="java.util.Map">
SELECT CONCAT(t1.hours,'时') hours, ifnull(t2.num, 0) num
FROM
(SELECT 0 hours
UNION ALL SELECT 1 hours
UNION ALL SELECT 2 hours
UNION ALL SELECT 3 hours
UNION ALL SELECT 4 hours
UNION ALL SELECT 5 hours
UNION ALL SELECT 6 hours
UNION ALL SELECT 7 hours
UNION ALL SELECT 8 hours
UNION ALL SELECT 9 hours
UNION ALL SELECT 10 hours
UNION ALL SELECT 11 hours
UNION ALL SELECT 12 hours
UNION ALL SELECT 13 hours
UNION ALL SELECT 14 hours
UNION ALL SELECT 15 hours
UNION ALL SELECT 16 hours
UNION ALL SELECT 17 hours
UNION ALL SELECT 18 hours
UNION ALL SELECT 19 hours
UNION ALL SELECT 20 hours
UNION ALL SELECT 21 hours
UNION ALL SELECT 22 hours
UNION ALL SELECT 23 hours
) t1
LEFT JOIN
(SELECT
hour(create_time) hours,count(*) num
FROM dc_event
WHERE date_format(create_time, '%Y%m%d') = date_format(now(), '%Y%m%d')
and event_type = '3' and event_subclass = '3-2'
GROUP BY date_format(create_time, '%Y%m%d %H'), hours) t2
ON t1.hours = t2.hours;
</select>
<select id="selectAccidentTypeStatistics" resultType="java.util.Map">
select t1.eventSubclass,t1.eventSubclassName,IFNULL(t2.num,0) num
from
(select '1-1' eventSubclass, '追尾' eventSubclassName
UNION ALL select '1-2' eventSubclass, '侧翻' eventSubclassName
UNION ALL select '1-3' eventSubclass, '撞护栏' eventSubclassName
UNION ALL select '1-4' eventSubclass, '自燃' eventSubclassName
UNION ALL select '1-5' eventSubclass, '其他事故' eventSubclassName) t1
LEFT JOIN
(select event_subclass, count(*) num
from dc_event
where event_type = '1'
and DATE_FORMAT(start_time,'%Y%m') = DATE_FORMAT(now(),'%Y%m')
group by event_subclass) t2
on t1.eventSubclass = t2.event_subclass
</select>
<select id="selectTrafficAccidents" resultType="java.util.Map">
SELECT CONCAT(t1.hours,'时') hours, ifnull(t2.num, 0) num
FROM
(SELECT 0 hours
UNION ALL SELECT 1 hours
UNION ALL SELECT 2 hours
UNION ALL SELECT 3 hours
UNION ALL SELECT 4 hours
UNION ALL SELECT 5 hours
UNION ALL SELECT 6 hours
UNION ALL SELECT 7 hours
UNION ALL SELECT 8 hours
UNION ALL SELECT 9 hours
UNION ALL SELECT 10 hours
UNION ALL SELECT 11 hours
UNION ALL SELECT 12 hours
UNION ALL SELECT 13 hours
UNION ALL SELECT 14 hours
UNION ALL SELECT 15 hours
UNION ALL SELECT 16 hours
UNION ALL SELECT 17 hours
UNION ALL SELECT 18 hours
UNION ALL SELECT 19 hours
UNION ALL SELECT 20 hours
UNION ALL SELECT 21 hours
UNION ALL SELECT 22 hours
UNION ALL SELECT 23 hours
) t1
LEFT JOIN
(SELECT
hour(create_time) hours,count(*) num
FROM dc_event
WHERE date_format(create_time, '%Y%m%d') = date_format(now(), '%Y%m%d')
and event_type = '1'
GROUP BY date_format(create_time, '%Y%m%d %H'), hours) t2
ON t1.hours = t2.hours;
</select>
<select id="selectCongestionLocation" resultType="java.util.Map">
select t1.eventSubclass,t1.eventSubclassLabel,IFNULL(t2.num,0) num
from
(select '4-1' eventSubclass,'道路拥堵' eventSubclassLabel
union all select '4-2' eventSubclass,'立交拥堵' eventSubclassLabel
union all select '4-3' eventSubclass,'收费站拥堵' eventSubclassLabel
union all select '4-4' eventSubclass,'服务区拥堵' eventSubclassLabel) t1
left join
(select event_type,event_subclass,count(*) num
from dc_event
where event_type = '4' and DATE_FORMAT(occurrence_time,'%Y%m') = DATE_FORMAT(now(),'%Y%m')
GROUP BY event_subclass
) t2
on t1.eventSubclass = t2.event_subclass
</select>
<select id="selectCongestionDuration" resultType="java.util.Map">
select
IFNULL(
sum(
case when date_format(#{startTime},'%Y-%m-%d %H:%i:%s') >= date_format(start_time,'%Y-%m-%d %H:%i:%s')
and (DATE_FORMAT(end_time,'%Y-%m-%d %H:%i:%s') >= DATE_FORMAT(#{endTime},'%Y-%m-%d %H:%i:%s') or ISNULL(end_time))
then TIMESTAMPDIFF(MINUTE,start_time,now())
when date_format(#{startTime},'%Y-%m-%d %H:%i:%s') >= date_format(start_time,'%Y-%m-%d %H:%i:%s')
and DATE_FORMAT(end_time,'%Y-%m-%d %H:%i:%s') &lt;= DATE_FORMAT(#{endTime},'%Y-%m-%d %H:%i:%s')
then TIMESTAMPDIFF(MINUTE,#{startTime},end_time)
when date_format(#{startTime},'%Y-%m-%d %H:%i:%s') &lt;= date_format(start_time,'%Y-%m-%d %H:%i:%s')
and (DATE_FORMAT(end_time,'%Y-%m-%d %H:%i:%s') >= DATE_FORMAT(#{endTime},'%Y-%m-%d %H:%i:%s') or ISNULL(end_time))
then TIMESTAMPDIFF(MINUTE,start_time,#{endTime})
when date_format(#{startTime},'%Y-%m-%d %H:%i:%s') &lt;= date_format(start_time,'%Y-%m-%d %H:%i:%s')
and DATE_FORMAT(end_time,'%Y-%m-%d %H:%i:%s') &lt;= DATE_FORMAT(#{endTime},'%Y-%m-%d %H:%i:%s')
then TIMESTAMPDIFF(MINUTE,start_time,end_time)
else 0 end
)
,0) minuteTime
from dc_event
where
event_type = '4' and date_format(start_time,'%Y-%m-%d %H:%i:%s') &lt;= date_format(#{endTime},'%Y-%m-%d %H:%i:%s')
and (date_format(end_time,'%Y-%m-%d %H:%i:%s') > date_format(#{startTime},'%Y-%m-%d %H:%i:%s') or ISNULL(end_time))
</select>
<select id="selectCongestionMileage" resultType="java.util.Map">
select t3.mileageType,IFNULL(t4.num,0) num
from
(select '0-2公里' mileageType
union all select '2-5公里' mileageType
union all select '5-8公里' mileageType
union all select '8-10公里' mileageType
union all select '10公里以上' mileageType) t3
left join
(select
case when t2.congestion_mileage &lt; 2 then '0-2公里'
when t2.congestion_mileage >= 2 and t2.congestion_mileage &lt; 5 then '2-5公里'
when t2.congestion_mileage >= 5 and t2.congestion_mileage &lt; 8 then '5-8公里'
when t2.congestion_mileage >= 8 and t2.congestion_mileage &lt; 10 then '8-10公里'
when t2.congestion_mileage >= 10 then '10公里以上'
else '' end mileageType,count(*) num
from dc_event t1
left join dc_event_traffic_congestion t2 on t1.id = t2.id
WHERE t1.event_type = '4' and DATE_FORMAT(t1.create_time,'%Y%m') = DATE_FORMAT(now(),'%Y%m')
GROUP BY mileageType
) t4
on t3.mileageType = t4.mileageType
</select>
</mapper>

BIN
zc-business/src/main/resources/wordTemplate/chartTemplate.docx

Binary file not shown.

BIN
zc-business/src/main/resources/wordTemplate/chartTemplate2.docx

Binary file not shown.

BIN
zc-business/src/main/resources/wordTemplate/wordTemplate.docx

Binary file not shown.
Loading…
Cancel
Save