Quellcode durchsuchen

区段+偏移量计算

zhanghaijun vor 2 Jahren
Ursprung
Commit
41fb8b33d6
25 geänderte Dateien mit 519 neuen und 564 gelöschten Zeilen
  1. 3 3
      train-server/src/main/java/top/haijunit/train/domain/constant/RouteStatusEnum.java
  2. 3 1
      train-server/src/main/java/top/haijunit/train/domain/constant/TrainConstant.java
  3. 0 25
      train-server/src/main/java/top/haijunit/train/domain/convert/RouteStatusConverter.java
  4. 25 0
      train-server/src/main/java/top/haijunit/train/domain/convert/SectionStatusConverter.java
  5. 2 3
      train-server/src/main/java/top/haijunit/train/domain/entity/RouteEntity.java
  6. 2 2
      train-server/src/main/java/top/haijunit/train/domain/entity/SelectionEntity.java
  7. 4 4
      train-server/src/main/java/top/haijunit/train/domain/entity/TrainEntity.java
  8. 2 0
      train-server/src/main/java/top/haijunit/train/domain/repository/RouteRepository.java
  9. 3 6
      train-server/src/main/java/top/haijunit/train/listener/ReportTimeHandle.java
  10. 10 7
      train-server/src/main/java/top/haijunit/train/listener/TrainApplication.java
  11. 39 71
      train-server/src/main/java/top/haijunit/train/service/RouteService.java
  12. 137 0
      train-server/src/main/java/top/haijunit/train/service/SectionService.java
  13. 5 5
      train-server/src/main/java/top/haijunit/train/service/SelectionService.java
  14. 0 57
      train-server/src/main/java/top/haijunit/train/service/SwitchService.java
  15. 0 142
      train-server/src/main/java/top/haijunit/train/simulation/domain/RouteItem.java
  16. 41 0
      train-server/src/main/java/top/haijunit/train/simulation/domain/SectionItem.java
  17. 9 52
      train-server/src/main/java/top/haijunit/train/simulation/domain/TrainItem.java
  18. 61 42
      train-server/src/main/java/top/haijunit/train/simulation/train/RouteComputerService.java
  19. 110 83
      train-server/src/main/java/top/haijunit/train/simulation/train/TrainComputerService.java
  20. 9 15
      train-server/src/main/java/top/haijunit/train/simulation/train/TrainSimulationHolder.java
  21. 11 6
      train-server/src/main/java/top/haijunit/train/utils/CalculatorUtil.java
  22. 4 4
      train-server/src/main/resources/db/changelog/train/db-server-train.xml
  23. 6 5
      train-server/src/main/resources/db/changelog/train/db_train.txt
  24. 33 0
      train-server/src/test/java/top/haijunit/train/RouteComputerTest.java
  25. 0 31
      train-server/src/test/java/top/haijunit/train/test/ConcurrentHashMapTest.java

+ 3 - 3
train-server/src/main/java/top/haijunit/train/domain/constant/RouteStatusEnum.java

@@ -14,7 +14,7 @@ import java.util.Objects;
  */
 @Getter
 @AllArgsConstructor
-public enum RouteStatusEnum {
+public enum SectionStatusEnum {
 
     OCCUPY(1, "占用(红光带)"),
     IDLE(2, "出清/空闲(灰光带)"),
@@ -25,8 +25,8 @@ public enum RouteStatusEnum {
     private final String describe;
 
     @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
-    public static RouteStatusEnum getByCode(Integer code) {
-        for (RouteStatusEnum genderEnum : values()) {
+    public static SectionStatusEnum getByCode(Integer code) {
+        for (SectionStatusEnum genderEnum : values()) {
             if (Objects.equals(genderEnum.getCode(), code)) {
                 return genderEnum;
             }

+ 3 - 1
train-server/src/main/java/top/haijunit/train/domain/constant/TrainConstant.java

@@ -10,7 +10,7 @@ import java.math.RoundingMode;
  */
 public interface TrainConstant {
 
-    BigDecimal SPEED_LIMIT = new BigDecimal("80");
+    BigDecimal SPEED_LIMIT = new BigDecimal("180");
     // 全线列车限速默认为80km/h 转换单位公式:速度(m/s) = 速度(km/h) * 1000 / 3600
     BigDecimal SPEED_LIMIT_EBI = SPEED_LIMIT.multiply(new BigDecimal("1000").divide(new BigDecimal("3600"), 10, RoundingMode.HALF_UP));
     // 推荐惰性速度为80-2=78km/h
@@ -25,4 +25,6 @@ public interface TrainConstant {
     long STOP_TIME_SECOND = 30;
     // 道岔转换要维持3S
     long SWITCH_CHANGE_TIME_SECOND = 3;
+
+    BigDecimal ROUTE_TRIGGER_OFFSET = new BigDecimal("100");
 }

+ 0 - 25
train-server/src/main/java/top/haijunit/train/domain/convert/RouteStatusConverter.java

@@ -1,25 +0,0 @@
-package top.haijunit.train.domain.convert;
-
-import jakarta.persistence.AttributeConverter;
-import jakarta.persistence.Converter;
-import top.haijunit.train.domain.constant.RouteStatusEnum;
-
-import java.util.Optional;
-
-/**
- * @author zhanghaijun
- * @date 2023/11/27 18:09
- * @description [一句话描述该类的功能]
- */
-@Converter(autoApply = true)
-public class RouteStatusConverter implements AttributeConverter<RouteStatusEnum, Integer> {
-    @Override
-    public Integer convertToDatabaseColumn(RouteStatusEnum direction) {
-        return Optional.of(direction).map(RouteStatusEnum::getCode).orElse(RouteStatusEnum.OCCUPY.getCode());
-    }
-
-    @Override
-    public RouteStatusEnum convertToEntityAttribute(Integer code) {
-        return RouteStatusEnum.getByCode(code);
-    }
-}

+ 25 - 0
train-server/src/main/java/top/haijunit/train/domain/convert/SectionStatusConverter.java

@@ -0,0 +1,25 @@
+package top.haijunit.train.domain.convert;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+import top.haijunit.train.domain.constant.SectionStatusEnum;
+
+import java.util.Optional;
+
+/**
+ * @author zhanghaijun
+ * @date 2023/11/27 18:09
+ * @description [一句话描述该类的功能]
+ */
+@Converter(autoApply = true)
+public class SectionStatusConverter implements AttributeConverter<SectionStatusEnum, Integer> {
+    @Override
+    public Integer convertToDatabaseColumn(SectionStatusEnum direction) {
+        return Optional.of(direction).map(SectionStatusEnum::getCode).orElse(SectionStatusEnum.OCCUPY.getCode());
+    }
+
+    @Override
+    public SectionStatusEnum convertToEntityAttribute(Integer code) {
+        return SectionStatusEnum.getByCode(code);
+    }
+}

+ 2 - 3
train-server/src/main/java/top/haijunit/train/domain/entity/RouteEntity.java

@@ -9,10 +9,9 @@ import jakarta.persistence.GenerationType;
 import jakarta.persistence.Id;
 import lombok.Data;
 import top.haijunit.train.domain.constant.DirectionEnum;
-import top.haijunit.train.domain.constant.RouteStatusEnum;
+import top.haijunit.train.domain.constant.SectionStatusEnum;
 import top.haijunit.train.domain.constant.SignalStateEnum;
 import top.haijunit.train.domain.constant.SwitchStateEnum;
-import top.haijunit.train.utils.NumberUtil;
 
 import java.io.Serializable;
 import java.math.BigDecimal;
@@ -51,7 +50,7 @@ public class RouteEntity implements Serializable {
     // 进路方向
     private DirectionEnum direction;
     // 进路状态
-    private RouteStatusEnum routeStatus;
+    private SectionStatusEnum routeStatus;
     // 下一条进路Id
     private Long nextId;
 

+ 2 - 2
train-server/src/main/java/top/haijunit/train/domain/entity/SelectionEntity.java

@@ -6,7 +6,7 @@ import jakarta.persistence.GenerationType;
 import jakarta.persistence.Id;
 import lombok.Data;
 import top.haijunit.train.domain.constant.DirectionEnum;
-import top.haijunit.train.domain.constant.RouteStatusEnum;
+import top.haijunit.train.domain.constant.SectionStatusEnum;
 import top.haijunit.train.utils.NumberUtil;
 
 import java.io.Serializable;
@@ -41,7 +41,7 @@ public class SelectionEntity implements Serializable {
     // 所属车站编号
     private Long stationId;
     // 区段状态
-    private RouteStatusEnum status;
+    private SectionStatusEnum status;
     // 是否是道岔
     private boolean isSwitch;
 

+ 4 - 4
train-server/src/main/java/top/haijunit/train/domain/entity/TrainEntity.java

@@ -28,8 +28,8 @@ public class TrainEntity implements Serializable {
     private String name;
     // 长度
     private BigDecimal trainLength;
-    // 初始位置公里标
-    private BigDecimal initKilometer;
-    // 运行方向
-    private DirectionEnum initDirection;
+    // 所在的区段
+    private Long sectionId;
+    // 区段的偏移量
+    private BigDecimal sectionOffset;
 }

+ 2 - 0
train-server/src/main/java/top/haijunit/train/domain/repository/RouteRepository.java

@@ -11,5 +11,7 @@ import top.haijunit.train.domain.entity.RouteEntity;
  */
 @Repository
 public interface RouteRepository extends JpaRepository<RouteEntity, Long> {
+
+    public RouteEntity findByNextId(Long nextId);
 }
 

+ 3 - 6
train-server/src/main/java/top/haijunit/train/listener/ReportTimeHandle.java

@@ -5,7 +5,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
-import top.haijunit.train.simulation.domain.RouteItem;
+import top.haijunit.train.simulation.domain.SectionItem;
 import top.haijunit.train.simulation.domain.TrainItem;
 import top.haijunit.train.simulation.train.RouteComputerService;
 import top.haijunit.train.simulation.train.TrainComputerService;
@@ -16,7 +16,6 @@ import top.haijunit.train.socket.TrainWebSocketUtils;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * @author zhanghaijun
@@ -43,9 +42,9 @@ public class ReportTimeHandle {
 
     /** 定时发送进路占用数据 */
     @Async
-    @Scheduled(fixedDelay = 1000)
+    @Scheduled(fixedDelay = 10000)
     public void sendRouterStatus() {
-        Map<Long, LinkedList<RouteItem>> routeMap = TrainSimulationHolder.getRouteMap();
+        Map<Long, LinkedList<SectionItem>> routeMap = TrainSimulationHolder.getRouteMap();
         TrainWebSocketUtils.sendMessage(CommandEnum.ROUTE_STATUS, new HashMap<>(routeMap));
     }
 
@@ -65,6 +64,4 @@ public class ReportTimeHandle {
             trainComputerService.run(trainItem);
         }
     }
-
-
 }

+ 10 - 7
train-server/src/main/java/top/haijunit/train/listener/TrainApplication.java

@@ -8,7 +8,9 @@ import org.springframework.stereotype.Component;
 import top.haijunit.train.domain.entity.SelectionEntity;
 import top.haijunit.train.domain.entity.TrainEntity;
 import top.haijunit.train.domain.repository.TrainRepository;
+import top.haijunit.train.service.SectionService;
 import top.haijunit.train.service.SelectionService;
+import top.haijunit.train.simulation.domain.SectionItem;
 import top.haijunit.train.simulation.domain.TrainItem;
 import top.haijunit.train.simulation.train.TrainSimulationHolder;
 
@@ -26,7 +28,7 @@ import java.util.List;
 public class TrainApplication implements ApplicationRunner {
 
     private final TrainRepository trainRepository;
-    private final SelectionService selectionService;
+    private final SectionService sectionService;
 
     @Override
     public void run(ApplicationArguments args) {
@@ -35,13 +37,14 @@ public class TrainApplication implements ApplicationRunner {
             add(110004L);
         }});
         for (TrainEntity train : list) {
-            SelectionEntity selection = selectionService.getSelection(train.getInitKilometer(), train.getInitDirection());
-            if (selection == null) {
-                log.error("初始化列车信息失败,列车编号:{},列车名称:{},初始位置:{},初始方向:{}", train.getId(), train.getName(), train.getInitKilometer(), train.getInitDirection());
-                continue;
+            try {
+                SectionItem sectionItem = sectionService.getSection(train.getSectionId());
+                if (null != sectionItem) {
+                    TrainSimulationHolder.addTrainItem(new TrainItem(train, sectionItem.getSectionName(), sectionItem.getDirection()));
+                }
+            } catch (Exception exception) {
+                log.error("列车初始化错误,立车:{},区段:{},便批量:{}", train.getId(), train.getSectionId(), train.getSectionOffset());
             }
-            TrainItem trainItem = new TrainItem(train, selection);
-            TrainSimulationHolder.addTrainItem(trainItem);
         }
     }
 }

+ 39 - 71
train-server/src/main/java/top/haijunit/train/service/RouteService.java

@@ -1,98 +1,66 @@
 package top.haijunit.train.service;
 
-import cn.hutool.core.collection.CollUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.MapUtils;
 import org.springframework.stereotype.Service;
-import top.haijunit.common.utils.StreamUtils;
-import top.haijunit.train.domain.constant.RouteStatusEnum;
+import top.haijunit.train.domain.constant.SwitchStateEnum;
+import top.haijunit.train.domain.constant.TrainConstant;
 import top.haijunit.train.domain.entity.RouteEntity;
-import top.haijunit.train.domain.entity.SelectionEntity;
-import top.haijunit.train.domain.entity.SignalEntity;
+import top.haijunit.train.domain.entity.SwitchEntity;
 import top.haijunit.train.domain.repository.RouteRepository;
-import top.haijunit.train.domain.repository.SelectionRepository;
-import top.haijunit.train.domain.repository.SignalRepository;
-import top.haijunit.train.simulation.domain.RouteItem;
-import top.haijunit.train.simulation.domain.TrainItem;
+import top.haijunit.train.domain.repository.SwitchRepository;
 
-import java.util.*;
-import java.util.stream.Collectors;
+import java.time.LocalDateTime;
+import java.util.Map;
 
 /**
  * @author zhanghaijun
- * @date 2023/11/29 06:54
- * @description [一句话描述该类的功能]
+ * @date 2023/11/29 06:23
+ * @description 道岔处理
  */
 @Slf4j
 @Service
 @RequiredArgsConstructor
 public class RouteService {
 
+    private final SwitchRepository switchRepository;
     private final RouteRepository routeRepository;
-    private final SelectionRepository selectionRepository;
-    private final SignalRepository signalRepository;
-    private final SelectionService selectionService;
 
-    public RouteItem toBuildRoute(RouteEntity route) {
-        if (route == null) {
-            return null;
-        }
-        List<SelectionEntity> selectionList = selectionRepository.findAllById(route.getSelectionList());
-        List<SignalEntity> signalList = signalRepository.findAllById(route.getSignalList());
-        return new RouteItem(route, new LinkedList<>(selectionList), new LinkedList<>(signalList));
-    }
 
-    public RouteItem getNextRoute(RouteItem route) {
-        Optional<RouteEntity> optional = routeRepository.findById(route.getNextId());
-        if (optional.isEmpty()) {
-            return null;
-        }
-        RouteEntity entity = optional.get();
-        LinkedList<SelectionEntity> selectionList = selectionService.getSelectionList(entity.getId());
-        // TODO 信号灯
-        RouteItem routeItem = new RouteItem(entity, selectionList, new LinkedList<>());
-        routeItem.setStatus(RouteStatusEnum.LOCK);
-        return routeItem;
+    public boolean changeSwitchState(Long routeId) {
+        RouteEntity route = routeRepository.findById(routeId).orElseThrow(() -> new IllegalArgumentException(String.format("---进路不存在, id: %s", routeId)));
+        return changeSwitchState(route.getSwitchMap());
     }
 
-    public LinkedList<RouteItem> getCurrentRouteList(TrainItem train) {
-        LinkedList<RouteEntity> list = new LinkedList<>();
-        for (RouteEntity route : routeRepository.findAll()) {
-            List<SelectionEntity> selectionList = selectionRepository.findAllById(route.getSelectionList());
-            for (SelectionEntity selection : selectionList) {
-                if (!StreamUtils.filter(list, item -> item.getId().equals(route.getId())).isEmpty()) {
-                    continue;
-                }
-                if (selection.isContain(train.getHeadKilometer(), train.getDirection())) {
-                    switch (selection.getDirection()) {
-                        case UP:
-                            list.addFirst(route);
-                            break;
-                        case DOWN:
-                            list.addLast(route);
-                    }
-                }
-                if (!StreamUtils.filter(list, item -> item.getId().equals(route.getId())).isEmpty()) {
-                    continue;
-                }
-                if (selection.isContain(train.getTailKilometer(), train.getDirection())) {
-                    switch (selection.getDirection()) {
-                        case UP:
-                            list.addLast(route);
-                            break;
-                        case DOWN:
-                            list.addFirst(route);
-                    }
-                }
+    /**
+     * 改变道岔的状态
+     *
+     * @param switchStateMap 改变后的的道岔状态
+     * @return 是否发生了变化,变化后需要等待时间
+     */
+    public boolean changeSwitchState(Map<Long, SwitchStateEnum> switchStateMap) {
+        boolean isChanged = false;
+        if (MapUtils.isEmpty(switchStateMap)) {
+            return false;
+        }
+        for (Map.Entry<Long, SwitchStateEnum> entry : switchStateMap.entrySet()) {
+            SwitchEntity switchEntity = switchRepository.findById(entry.getKey()).orElse(null);
+            if (switchEntity == null) {
+                log.error("-----道岔不存在, id: {}", entry.getKey());
+                return false;
+            }
+            if (switchEntity.getSwitchState() != entry.getValue()) {
+                switchEntity.setSwitchState(entry.getValue());
+                switchEntity.setUpdateStateTime(LocalDateTime.now());
+                switchRepository.save(switchEntity);
+                isChanged = true;
+            } else if (LocalDateTime.now().isBefore(switchEntity.getUpdateStateTime().plusSeconds(TrainConstant.SWITCH_CHANGE_TIME_SECOND))) {
+                // 道岔切换状态需要时间3s
+                isChanged = true;
             }
         }
-        return list.stream().map(entity -> {
-            LinkedList<SelectionEntity> selectionList = selectionService.getSelectionList(entity.getId(), train.getDirection(), train.getHeadKilometer(), train.getTailKilometer());
-            // TODO 型号灯
-            RouteItem routeItem = new RouteItem(entity, selectionList, new LinkedList<>());
-            routeItem.setStatus(RouteStatusEnum.OCCUPY);
-            return routeItem;
-        }).collect(Collectors.toCollection(LinkedList::new));
+        return isChanged;
     }
 
 }

+ 137 - 0
train-server/src/main/java/top/haijunit/train/service/SectionService.java

@@ -0,0 +1,137 @@
+package top.haijunit.train.service;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import top.haijunit.common.utils.StreamUtils;
+import top.haijunit.train.domain.constant.SectionStatusEnum;
+import top.haijunit.train.domain.constant.TrainConstant;
+import top.haijunit.train.domain.entity.RouteEntity;
+import top.haijunit.train.domain.entity.SelectionEntity;
+import top.haijunit.train.domain.repository.RouteRepository;
+import top.haijunit.train.domain.repository.SelectionRepository;
+import top.haijunit.train.simulation.domain.SectionItem;
+import top.haijunit.train.utils.NumberUtil;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author zhanghaijun
+ * @date 2023/12/9 11:14
+ * @description [一句话描述该类的功能]
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SectionService {
+
+    private final RouteRepository routeRepository;
+    private final SelectionRepository selectionRepository;
+
+    /**
+     * 根据sectionId获取section
+     *
+     * @param sectionId 当前区段
+     * @param offset    偏移量
+     * @param distance  距离 可能是负数,计算车尾的位置
+     * @return 前进指定距离后的所在区段
+     */
+    public SectionItem getSection(Long sectionId, BigDecimal offset, BigDecimal distance) {
+        SectionItem sectionItem = this.getSection(sectionId);
+        BigDecimal decimal = NumberUtil.add(offset, NumberUtil.mul(distance, sectionItem.getDirection().getFactor()));
+        if (NumberUtil.isGreaterOrEqual(decimal, sectionItem.getSectionLength())) {
+            Long sId = switch (sectionItem.getDirection()) {
+                case UP -> sectionItem.getSectionPreId();
+                case DOWN -> sectionItem.getSectionNextId();
+            };
+            return getSection(sId, new BigDecimal("0"), NumberUtil.sub(decimal, sectionItem.getSectionLength()));
+        }
+        return sectionItem;
+    }
+
+    public SectionItem getSection(Long sectionId) {
+        return StreamUtils.filter(getAllSection(), item -> item.getSectionId().equals(sectionId)).stream().findFirst().orElseThrow(() -> new IllegalArgumentException(String.format("不存在该区段,id: %s", sectionId)));
+    }
+
+    public List<SectionItem> getSectionAll(Long routeId) {
+        return StreamUtils.filter(getAllSection(), item -> item.getRouteId().equals(routeId));
+    }
+
+    public Long getRouteNextId(Long sectionId) {
+        SectionItem sectionItem = this.getSection(sectionId);
+        RouteEntity route = routeRepository.findById(sectionItem.getRouteId()).orElseThrow(() -> new IllegalArgumentException(String.format("未找到进路数据,进路Id: %s", sectionItem.getRouteId())));
+        RouteEntity entity = routeRepository.findById(route.getNextId()).orElseThrow(() -> new IllegalArgumentException(String.format("未找到进路数据,进路Id: %s", route.getNextId())));
+        if (null == entity) {
+            throw new IllegalArgumentException(String.format("没有找到下一个进路,区段Id:%s", sectionId));
+        }
+        return entity.getId();
+    }
+
+    public boolean isTrigger(Long sectionId, BigDecimal offset) {
+        SectionItem section = this.getSection(sectionId);
+        RouteEntity route = routeRepository.findById(section.getRouteId()).orElseThrow(() -> new IllegalArgumentException(String.format("不存在该进路,id: %s", section.getRouteId())));
+        if (sectionId.equals(CollUtil.getLast(route.getSelectionList()))) {
+            // 进入最后一个区段才触发下一个进路
+            BigDecimal distance = NumberUtil.add(offset, TrainConstant.ROUTE_TRIGGER_OFFSET);
+            return NumberUtil.isLessOrEqual(section.getSectionLength(), distance);
+        }
+        return false;
+    }
+
+    /**
+     * 获取所有进路的区段
+     *
+     * @return 全部进路的区段
+     */
+    public List<SectionItem> getAllSection() {
+        List<SectionItem> sectionList = new ArrayList<>();
+        List<RouteEntity> routeList = routeRepository.findAll();
+        for (RouteEntity route : routeList) {
+            List<SelectionEntity> selectionList = selectionRepository.findAllById(route.getSelectionList());
+            for (int i = 0; i < selectionList.size(); i++) {
+                SelectionEntity selection = selectionList.get(i);
+                // 区段数据
+                SectionItem sectionItem = new SectionItem();
+                sectionItem.setSectionId(selection.getId());
+                sectionItem.setSectionName(selection.getName());
+                sectionItem.setSectionLength(selection.getSelectionLength());
+                sectionItem.setStartKilometer(selection.getStartKilometer());
+                sectionItem.setSwitch(selection.isSwitch());
+                sectionItem.setDirection(selection.getDirection());
+                // 进路数据
+                sectionItem.setRouteId(route.getId());
+                sectionItem.setRouteName(route.getName());
+                sectionItem.setSectionStatus(SectionStatusEnum.IDLE);
+                // 区段连接数据
+                if (i == 0) {
+                    // 进路的第一个区段
+                    sectionItem.setSectionPreId(this.getRouteSectionLast(route.getId()));
+                    sectionItem.setSectionNextId(Optional.of(selectionList.get(i + 1)).map(SelectionEntity::getId).orElse(-1L));
+                } else if (i + 1 >= selectionList.size()) {
+                    // 进路的最后一个区段
+                    sectionItem.setSectionPreId(Optional.of(selectionList.get(i - 1)).map(SelectionEntity::getId).orElse(-1L));
+                    sectionItem.setSectionNextId(this.getRouteSectionFirst(route.getNextId()));
+                } else {
+                    sectionItem.setSectionPreId(Optional.of(selectionList.get(i - 1)).map(SelectionEntity::getId).orElse(-1L));
+                    sectionItem.setSectionNextId(Optional.of(selectionList.get(i + 1)).map(SelectionEntity::getId).orElse(-1L));
+                }
+                sectionList.add(sectionItem);
+            }
+        }
+        return sectionList;
+    }
+
+    private Long getRouteSectionFirst(Long routeId) {
+        Optional<RouteEntity> optional = routeRepository.findById(routeId);
+        return optional.map(item -> CollUtil.getFirst(item.getSelectionList())).orElse(-1L);
+    }
+
+    private Long getRouteSectionLast(Long routeNextId) {
+        RouteEntity entity = routeRepository.findByNextId(routeNextId);
+        return Optional.of(entity).map(item -> CollUtil.getLast(item.getSelectionList())).orElse(-1L);
+    }
+}

+ 5 - 5
train-server/src/main/java/top/haijunit/train/service/SelectionService.java

@@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import top.haijunit.train.domain.constant.DirectionEnum;
-import top.haijunit.train.domain.constant.RouteStatusEnum;
+import top.haijunit.train.domain.constant.SectionStatusEnum;
 import top.haijunit.train.domain.entity.RouteEntity;
 import top.haijunit.train.domain.entity.SelectionEntity;
 import top.haijunit.train.domain.entity.SignStopEntity;
@@ -53,7 +53,7 @@ public class SelectionService {
         ArrayList<Long> seleIds = routeRepository.findById(routeId).map(RouteEntity::getSelectionList).orElseThrow(() -> new IllegalArgumentException("进路不存在,进路Id:" + routeId));
         List<SelectionEntity> selectionList = selectionRepository.findAllById(seleIds);
         for (SelectionEntity entity : selectionList) {
-            entity.setStatus(RouteStatusEnum.LOCK);
+            entity.setStatus(SectionStatusEnum.LOCK);
         }
         return new LinkedList<>(selectionList);
     }
@@ -75,9 +75,9 @@ public class SelectionService {
                 // 列车还未驶过该区段
                 if (entity.isContain(headKilometer, direction) || entity.isContain(tailKilometer, direction)) {
                     // 车头或者车尾在区段内,则是占用状态
-                    entity.setStatus(RouteStatusEnum.OCCUPY);
+                    entity.setStatus(SectionStatusEnum.OCCUPY);
                 } else {
-                    entity.setStatus(RouteStatusEnum.LOCK);
+                    entity.setStatus(SectionStatusEnum.LOCK);
                 }
                 list.addLast(entity);
             }
@@ -85,7 +85,7 @@ public class SelectionService {
         return list;
     }
 
-    public HashMap<Long, SignStopEntity> getStopSignalMap(LinkedList<Long> selectionAllList) {
+    public HashMap<Long, SignStopEntity> getStopSignalMap(List<Long> selectionAllList) {
         return signStopRepository.findAllById(selectionAllList).stream().collect(HashMap::new, (map, entity) -> map.put(entity.getSelectionId(), entity), HashMap::putAll);
     }
 }

+ 0 - 57
train-server/src/main/java/top/haijunit/train/service/SwitchService.java

@@ -1,57 +0,0 @@
-package top.haijunit.train.service;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections4.MapUtils;
-import org.springframework.stereotype.Service;
-import top.haijunit.train.domain.constant.SwitchStateEnum;
-import top.haijunit.train.domain.constant.TrainConstant;
-import top.haijunit.train.domain.entity.SwitchEntity;
-import top.haijunit.train.domain.repository.SwitchRepository;
-
-import java.time.LocalDateTime;
-import java.util.Map;
-
-/**
- * @author zhanghaijun
- * @date 2023/11/29 06:23
- * @description 道岔处理
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class SwitchService {
-
-    private final SwitchRepository switchRepository;
-
-    /**
-     * 改变道岔的状态
-     *
-     * @param switchStateMap 改变后的的道岔状态
-     * @return 是否发生了变化,变化后需要等待时间
-     */
-    public boolean changeSwitchState(Map<Long, SwitchStateEnum> switchStateMap) {
-        boolean isChanged = false;
-        if (MapUtils.isEmpty(switchStateMap)) {
-            return false;
-        }
-        for (Map.Entry<Long, SwitchStateEnum> entry : switchStateMap.entrySet()) {
-            SwitchEntity switchEntity = switchRepository.findById(entry.getKey()).orElse(null);
-            if (switchEntity == null) {
-                log.error("-----道岔不存在, id: {}", entry.getKey());
-                return false;
-            }
-            if (switchEntity.getSwitchState() != entry.getValue()) {
-                switchEntity.setSwitchState(entry.getValue());
-                switchEntity.setUpdateStateTime(LocalDateTime.now());
-                switchRepository.save(switchEntity);
-                isChanged = true;
-            } else if (LocalDateTime.now().isBefore(switchEntity.getUpdateStateTime().plusSeconds(TrainConstant.SWITCH_CHANGE_TIME_SECOND))) {
-                // 道岔切换状态需要时间3s
-                isChanged = true;
-            }
-        }
-        return isChanged;
-    }
-
-}

+ 0 - 142
train-server/src/main/java/top/haijunit/train/simulation/domain/RouteItem.java

@@ -1,142 +0,0 @@
-package top.haijunit.train.simulation.domain;
-
-import lombok.Getter;
-import lombok.Setter;
-import top.haijunit.train.domain.constant.DirectionEnum;
-import top.haijunit.train.domain.constant.RouteStatusEnum;
-import top.haijunit.train.domain.constant.SwitchStateEnum;
-import top.haijunit.train.domain.entity.RouteEntity;
-import top.haijunit.train.domain.entity.SelectionEntity;
-import top.haijunit.train.domain.entity.SignalEntity;
-import top.haijunit.train.utils.NumberUtil;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-/**
- * @author zhanghaijun
- * @date 2023/12/3 01:42
- * @description 用于进路计算
- */
-@Getter
-@Setter
-public class RouteItem implements Serializable {
-
-    // 进路Id
-    private Long id;
-    // 进路名称
-    private String name;
-    // 触发点位置
-    private BigDecimal trigPosition;
-    // 进路方向
-    private DirectionEnum direction;
-    // 进路状态
-    private RouteStatusEnum routeStatus;
-    // 下一条进路Id
-    private Long nextId;
-
-    // 区段
-    private LinkedList<SelectionEntity> selectionList;
-    // 信号灯
-    private LinkedList<SignalEntity> signalList;
-    // 道岔的预定状态
-    private Map<Long, SwitchStateEnum> switchStateMap;
-
-    public RouteItem(RouteEntity route, LinkedList<SelectionEntity> selectionList, LinkedList<SignalEntity> signalList) {
-        this.id = route.getId();
-        this.name = route.getName();
-        this.trigPosition = route.getTrigPosition();
-        this.direction = route.getDirection();
-        this.routeStatus = RouteStatusEnum.IDLE;
-        this.nextId = route.getNextId();
-        this.switchStateMap = route.getSwitchMap();
-        this.selectionList = selectionList;
-        this.signalList = signalList;
-    }
-
-
-    /** 进路开始公里标 */
-    public BigDecimal getHeadKilometer() {
-        return this.getSelectionList().getFirst().getStartKilometer();
-    }
-
-    /** 进路结束公里标 */
-    public BigDecimal getTailKilometer() {
-        return this.getSelectionList().getLast().getEndKilometer();
-    }
-
-    public LinkedList<Long> getSelectionIds() {
-        return this.getSelectionList().stream().map(SelectionEntity::getId).collect(Collectors.toCollection(LinkedList::new));
-    }
-
-    public LinkedList<String> getSelectionNames() {
-        return this.getSelectionList().stream().map(SelectionEntity::getName).collect(Collectors.toCollection(LinkedList::new));
-    }
-
-    /**
-     * @param kilometer 公里标
-     * @param direction 方向
-     * @return 公里标是否在当前进路中
-     */
-    public boolean isContain(BigDecimal kilometer, DirectionEnum direction) {
-        if (this.getDirection() != direction) {
-            return false;
-        }
-        if (this.getSelectionList().isEmpty()) {
-            return false;
-        }
-        return NumberUtil.isContain(kilometer, this.getHeadKilometer(), this.getTailKilometer());
-    }
-
-    /**
-     * @param tailKilometer 公里标
-     * @param direction     方向
-     * @return 是否已经通过当前进路
-     */
-    public boolean isPass(BigDecimal headKilometer, BigDecimal tailKilometer, DirectionEnum direction) {
-        if (!isContain(headKilometer, direction)) {
-            return true;
-        }
-        if (!isContain(tailKilometer, direction)) {
-            return true;
-        }
-        return switch (direction) {
-            // 上行 车尾 < 进路的开始里程
-            case UP -> NumberUtil.isLess(tailKilometer, this.getHeadKilometer());
-            // 下行 车位 => 进路的结束里程
-            case DOWN -> NumberUtil.isGreaterOrEqual(tailKilometer, this.getTailKilometer());
-        };
-    }
-
-    // 是否触发下一个进路
-    public boolean isTrigger(BigDecimal kilometer, DirectionEnum direction) {
-        if (!isContain(kilometer, direction)) {
-            // 车头不在进路中,直接返回false
-            return false;
-        }
-        return switch (direction) {
-            // 上行 进路开始公里数 <= 车头公里数 <= 触发点公里数
-            case UP -> NumberUtil.isIn(kilometer, this.getHeadKilometer(), this.getTrigKilometer());
-            // 下行 触发点公里数 <= 车头公里数 <= 进路结束公里数
-            case DOWN -> NumberUtil.isIn(kilometer, this.getTrigKilometer(), this.getTailKilometer());
-        };
-    }
-
-    public BigDecimal getTrigKilometer() {
-        return this.getHeadKilometer().add(this.getTrigPosition());
-    }
-
-
-    // 是否包含道岔
-    public boolean isEmptySwitch() {
-        return this.getSwitchStateMap().isEmpty();
-    }
-
-    // 改变状态
-    public void setStatus(RouteStatusEnum status) {
-        this.routeStatus = status;
-    }
-}

+ 41 - 0
train-server/src/main/java/top/haijunit/train/simulation/domain/SectionItem.java

@@ -0,0 +1,41 @@
+package top.haijunit.train.simulation.domain;
+
+import lombok.Data;
+import top.haijunit.train.domain.constant.DirectionEnum;
+import top.haijunit.train.domain.constant.SectionStatusEnum;
+
+import java.math.BigDecimal;
+
+/**
+ * @author zhanghaijun
+ * @date 2023/12/9 11:08
+ * @description [一句话描述该类的功能]
+ */
+@Data
+public class SectionItem {
+
+    // 区段Id
+    private Long sectionId;
+    // 区段名称
+    private String sectionName;
+    // 区段长度
+    private BigDecimal sectionLength;
+    // 区段状态
+    private SectionStatusEnum sectionStatus;
+    // 开始公里标
+    private BigDecimal startKilometer;
+    // 是否是道岔
+    private boolean isSwitch;
+    // 方向
+    private DirectionEnum direction;
+
+    // 进路Id
+    private Long routeId;
+    // 进路名称
+    private String routeName;
+
+    // 下一个区段Id
+    private Long sectionNextId;
+    // 上一个区段Id
+    private Long sectionPreId;
+}

+ 9 - 52
train-server/src/main/java/top/haijunit/train/simulation/domain/TrainItem.java

@@ -4,14 +4,10 @@ import lombok.Getter;
 import lombok.Setter;
 import top.haijunit.train.domain.constant.DirectionEnum;
 import top.haijunit.train.domain.constant.TrainConstant;
-import top.haijunit.train.domain.entity.SelectionEntity;
 import top.haijunit.train.domain.entity.TrainEntity;
 import top.haijunit.train.utils.CalculatorUtil;
-import top.haijunit.train.utils.NumberUtil;
 
 import java.math.BigDecimal;
-import java.time.Duration;
-import java.time.LocalTime;
 
 /**
  * @author zhang
@@ -29,8 +25,6 @@ public class TrainItem {
     // 列车编号
     private final String trainNo;
 
-    // 公里标
-    private BigDecimal kilometer;
     // 运行方向
     private DirectionEnum direction;
     // 当前区段Id
@@ -40,64 +34,27 @@ public class TrainItem {
     // 偏移
     private BigDecimal offset;
 
-    private LocalTime updateTime;
+    private long updateTimeMillis;
     // 运行速度
     private BigDecimal speed;
     // 运行加速度
     private BigDecimal acceleration;
 
-    public TrainItem(TrainEntity train, SelectionEntity selection) {
+    public TrainItem(TrainEntity train, String sectionName, DirectionEnum direction) {
         this.trainId = train.getId();
         this.trainLength = train.getTrainLength();
         this.trainNo = train.getName();
-        this.kilometer = train.getInitKilometer();
-        this.direction = train.getInitDirection();
+        this.direction = direction;
         this.speed = new BigDecimal("0.00");
         this.acceleration = new BigDecimal("0.00");
-        this.updateTime = LocalTime.now().minus(Duration.ofSeconds(TrainConstant.STOP_TIME_SECOND));
-        this.setHeadKilometer(this.updateTime, train.getInitKilometer(), selection);
-    }
-
-    // 区段Id
-    public Long getSelectionId() {
-        return this.blockId;
-    }
-
-    // 车头的公里标
-    public BigDecimal getHeadKilometer() {
-        return this.kilometer;
-    }
-
-    /**
-     * 更新列车位置信息
-     *
-     * @param updateTime 更新时间
-     * @param kilometer  公里标
-     * @param selection  区段
-     */
-    public void setHeadKilometer(LocalTime updateTime, BigDecimal kilometer, SelectionEntity selection) {
-        if (this.getUpdateTime() == null) {
-            return;
-        }
-        BigDecimal v = CalculatorUtil.velocity(this.getSpeed(), this.getAcceleration(), Duration.between(this.getUpdateTime(), updateTime).abs());
-        this.updateTime = updateTime;
-        this.speed = v;
-        this.kilometer = kilometer;
-        this.blockId = selection.getId();
-        this.blockNo = selection.getName();
-        this.offset = NumberUtil.sub(kilometer, selection.getStartKilometer()).abs();
-    }
-
-    // 车尾的公里标
-    public BigDecimal getTailKilometer() {
-        return switch (this.getDirection()) {
-            case UP -> this.getHeadKilometer().add(this.getTrainLength());
-            case DOWN -> this.getHeadKilometer().subtract(this.getTrainLength());
-        };
+        this.updateTimeMillis = System.currentTimeMillis() - (TrainConstant.STOP_TIME_SECOND * 1000);
+        this.blockId = train.getSectionId();
+        this.blockNo = sectionName;
+        this.offset = train.getSectionOffset();
     }
 
     // 计算时间内的运行距离
-    public BigDecimal getDistance(LocalTime time) {
-        return CalculatorUtil.distanceStopLimit(this.getSpeed(), this.getAcceleration(), Duration.between(this.getUpdateTime(), time));
+    public BigDecimal getDistance(long timeMillis) {
+        return CalculatorUtil.distanceStopLimit(this.getSpeed(), this.getAcceleration(), Math.abs(timeMillis - this.getUpdateTimeMillis()));
     }
 }

+ 61 - 42
train-server/src/main/java/top/haijunit/train/simulation/train/RouteComputerService.java

@@ -4,17 +4,17 @@ import cn.hutool.core.collection.CollUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import top.haijunit.common.utils.ErrorUtil;
 import top.haijunit.common.utils.StreamUtils;
-import top.haijunit.train.domain.constant.RouteStatusEnum;
-import top.haijunit.train.domain.entity.SelectionEntity;
+import top.haijunit.train.domain.constant.SectionStatusEnum;
+import top.haijunit.train.service.SectionService;
 import top.haijunit.train.service.RouteService;
-import top.haijunit.train.service.SelectionService;
-import top.haijunit.train.service.SwitchService;
-import top.haijunit.train.simulation.domain.RouteItem;
+import top.haijunit.train.simulation.domain.SectionItem;
 import top.haijunit.train.simulation.domain.TrainItem;
+import top.haijunit.train.utils.NumberUtil;
 
+import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -27,69 +27,88 @@ import java.util.stream.Collectors;
 @RequiredArgsConstructor
 public class RouteComputerService {
 
-    private final SwitchService switchService;
     private final RouteService routeService;
+    private final SectionService sectionService;
 
     public void run(TrainItem train) {
-        // 更新进路共享数据
-        LinkedList<RouteItem> list = routeCompute(train);
-        // log.info("------------");
-        // log.info("列车的位置:{},方向:{}", train.getHeadKilometer(), train.getDirection());
-        // log.info("进路资源:{}", list.stream().map(item -> item.getId() + "--" + item.getRouteStatus().getDescribe() + "--" + item.getSelectionNames()).collect(Collectors.joining(",")));
-        // log.info("------------");
-        if (CollUtil.isNotEmpty(list)) {
-            TrainSimulationHolder.addRouteList(train.getTrainId(), list);
+        try {
+            LinkedList<SectionItem> list = routeCompute(train);
+            log.info("---进路数据,列车:{},进路:{}", train.getTrainNo(), list.stream().map(item -> {
+                return item.getRouteId() + "-" + item.getSectionName();
+            }).collect(Collectors.joining(",")));
+            if (CollUtil.isNotEmpty(list)) {
+                // 更新进路共享数据
+                TrainSimulationHolder.addRouteList(train.getTrainId(), list);
+            }
+        } catch (Exception exception) {
+            log.error("进路计算错误:{}", ErrorUtil.errorInfoToString(exception));
         }
     }
 
-    public LinkedList<RouteItem> routeCompute(TrainItem train) {
+    public LinkedList<SectionItem> routeCompute(TrainItem train) {
         // 1、是否触发进路计算  条件:下一个进路还已经触发 && 列车车头已经越过当前进路的触发点
         // 2、寻找下一跳进路
         // 3、下一条进路是否为空闲
         // 4、下一条进路是否包括道岔,道岔状态是否正确
         // 5、将下一条进路状态置为锁定状态
         // 6、释放车尾已过的进路
-        LinkedList<RouteItem> routeList = TrainSimulationHolder.getRouteList(train.getTrainId());
-        RouteItem currentRoute = TrainSimulationHolder.getCurrentRoute(train);
-        // 释放资源 进路的全部区段的公里标 小于 当前列车所在的区段
-        routeList.removeIf(item -> item.isPass(train.getHeadKilometer(), train.getTailKilometer(), train.getDirection()));
-        if (null == currentRoute) {
-            log.info("初始化,没有找到当前的进路信息,列车: {}", train.getTrainId());
-            // 初始化为空处理
-            routeList = routeService.getCurrentRouteList(train);
-            if (CollUtil.isEmpty(routeList)) {
-                log.error("初始化,车辆位置错误,未找到对应的进路,方向:{},列车位置:{}", train.getDirection(), train.getKilometer());
-                return routeList;
-            }
-            currentRoute = switch (train.getDirection()) {
-                case UP -> routeList.getFirst();
-                case DOWN -> routeList.getLast();
-            };
-            if (currentRoute.getSelectionList().isEmpty()) {
-                log.error("错误进路中区段占用为空:进路:{},列车位置:{}", currentRoute.getId(), train.getKilometer());
+        LinkedList<SectionItem> routeList = this.getSectionList(train);
+        if (CollUtil.isEmpty(routeList)) {
+            log.info("初始化进路计算,列车: {}", train.getTrainId());
+            // 车头所在的区段
+            SectionItem sectionHead = sectionService.getSection(train.getBlockId());
+            sectionHead.setSectionStatus(SectionStatusEnum.OCCUPY);
+            // 车尾所在的区段
+            SectionItem sectionTail = sectionService.getSection(train.getBlockId(), train.getOffset(), NumberUtil.mul(train.getTrainLength(), -1));
+            routeList.addFirst(sectionHead);
+            if (!sectionHead.getSectionId().equals(sectionTail.getSectionId())) {
+                sectionTail.setSectionStatus(SectionStatusEnum.OCCUPY);
+                routeList.addLast(sectionTail);
             }
         }
-        // 开始计算下一跳进路
-        if (!currentRoute.isTrigger(train.getHeadKilometer(), train.getDirection())) {
+        if (!sectionService.isTrigger(train.getBlockId(), train.getOffset())) {
             // 没有触发进路
             return routeList;
         }
-        RouteItem nextRoute = routeService.getNextRoute(currentRoute);
-        if (null == nextRoute || !StreamUtils.filter(routeList, (item -> item.getId().equals(nextRoute.getId()))).isEmpty()) {
-            // 没有找到下一条进路 || 已在已有进路列表中
+        Long routeId = sectionService.getRouteNextId(train.getBlockId());
+        if (CollUtil.isNotEmpty(StreamUtils.filter(routeList, item -> item.getRouteId().equals(routeId)))) {
+            // 已经添加过了
             return routeList;
         }
         // a)检查进路当前是否为出清/空闲状态,否则返回触发失败;
-        if (!TrainSimulationHolder.isRouteIdle(nextRoute.getId())) {
+        if (!TrainSimulationHolder.isRouteIdle(routeId)) {
             // 下一跳进路为非出清状态
             return routeList;
         }
-        if (nextRoute.isEmptySwitch() && switchService.changeSwitchState(nextRoute.getSwitchStateMap())) {
+        if (routeService.changeSwitchState(routeId)) {
             // b)检查进路中道岔状态是否与进路要求的状态一致 切换道岔需要时间
             return routeList;
         }
         // c)出清(如果包括道岔,道岔已就位),将进路至于锁闭状态
-        routeList.addLast(nextRoute);
+        routeList.addAll(sectionService.getSectionAll(routeId));
+        return routeList;
+    }
+
+    private LinkedList<SectionItem> getSectionList(TrainItem train) {
+        LinkedList<SectionItem> routeList = TrainSimulationHolder.getRouteList(train.getTrainId());
+        if (CollUtil.isEmpty(routeList)) {
+            return new LinkedList<>();
+        }
+        Long sectionId = switch (train.getDirection()) {
+            // 车头所在的区段
+            case UP -> train.getBlockId();
+            // 车尾的所在的区段
+            case DOWN -> sectionService.getSection(train.getBlockId(), train.getOffset(), NumberUtil.mul(train.getTrainLength(),-1)).getSectionId();
+        };
+        Iterator<SectionItem> iterator = routeList.iterator();
+        while (iterator.hasNext()) {
+            SectionItem current = iterator.next();
+            if (current.getSectionId().equals(sectionId)) {
+                break;
+            }
+            iterator.remove();
+        }
         return routeList;
     }
 }
+

+ 110 - 83
train-server/src/main/java/top/haijunit/train/simulation/train/TrainComputerService.java

@@ -1,13 +1,15 @@
 package top.haijunit.train.simulation.train;
 
+import cn.hutool.core.collection.CollUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import top.haijunit.common.utils.ErrorUtil;
+import top.haijunit.common.utils.StreamUtils;
 import top.haijunit.train.domain.constant.TrainConstant;
-import top.haijunit.train.domain.entity.SelectionEntity;
 import top.haijunit.train.domain.entity.SignStopEntity;
 import top.haijunit.train.service.SelectionService;
-import top.haijunit.train.simulation.domain.RouteItem;
+import top.haijunit.train.simulation.domain.SectionItem;
 import top.haijunit.train.simulation.domain.TrainItem;
 import top.haijunit.train.utils.CalculatorUtil;
 import top.haijunit.train.utils.NumberUtil;
@@ -15,11 +17,8 @@ import top.haijunit.train.utils.NumberUtil;
 import java.math.BigDecimal;
 import java.time.Duration;
 import java.time.LocalTime;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.OptionalInt;
+import java.util.*;
 import java.util.stream.Collectors;
-import java.util.stream.IntStream;
 
 /**
  * @author zhanghaijun
@@ -32,103 +31,131 @@ import java.util.stream.IntStream;
 public class TrainComputerService {
     private final SelectionService selectionService;
 
-
     public void run(TrainItem trainItem) {
-        TrainItem item = this.runComputer(trainItem);
-        if (null != item) {
-            TrainSimulationHolder.addTrainItem(item);
+        try {
+            TrainItem item = this.runComputer(trainItem);
+            if (null != item) {
+                log.info("列车{} 区段:{},偏移量:{},速度:{},加速度:{}", trainItem.getTrainNo(), trainItem.getBlockNo(), trainItem.getOffset(), trainItem.getSpeed(), trainItem.getAcceleration());
+                TrainSimulationHolder.addTrainItem(item);
+            }
+        } catch (Exception exception) {
+            log.error("列车行驶计算错误:{}", ErrorUtil.errorInfoToString(exception));
         }
     }
 
-    public TrainItem runComputer(TrainItem trainItem) {
-        // 进路数据
-        LinkedList<RouteItem> routeList = TrainSimulationHolder.getRouteList(trainItem.getTrainId());
-        // 区段数据
-        LinkedList<SelectionEntity> selectionAllList = new LinkedList<>();
-        for (RouteItem item : routeList) {
-            selectionAllList.addAll(item.getSelectionList());
-        }
-        // 停车标数据
-        HashMap<Long, SignStopEntity> stopSignalMap = selectionService.getStopSignalMap(selectionAllList.stream().map(SelectionEntity::getId).collect(Collectors.toCollection(LinkedList::new)));
-        // 当前的区段下标
-        OptionalInt optionIndex = IntStream.range(0, selectionAllList.size()).filter(i -> {
-            SelectionEntity entity = selectionAllList.get(i);
-            return NumberUtil.isContain(trainItem.getHeadKilometer(), entity.getStartKilometer(), entity.getEndKilometer());
-        }).findFirst();
-        if (optionIndex.isEmpty()) {
-            log.error("列车没有进路,列车位置: {},列车速度: {},列车方向: {}", trainItem.getHeadKilometer(), trainItem.getSpeed(), trainItem.getDirection());
+    public TrainItem runComputer(TrainItem train) {
+        // 进路区段数据
+        LinkedList<SectionItem> sectionList = TrainSimulationHolder.getRouteList(train.getTrainId());
+        if (CollUtil.isEmpty(sectionList)) {
+            log.error("没有列车的进路数据,请检查进路计算是否正常,列车name:{}", train.getTrainNo());
             return null;
         }
-        // 计算前的区段下标
-        int index = optionIndex.getAsInt();
-        // 计算后的区段下标
-        int currentIndex = selectionAllList.size() - 1;
         // 获取当前的时间
-        LocalTime now = LocalTime.now();
+        long nowMillis = System.currentTimeMillis();
         // 1、距离上一个周期 列车运行的距离
-        BigDecimal decimal = trainItem.getDistance(now);
-        if (NumberUtil.isLessOrEqual(decimal, BigDecimal.ZERO) && Math.abs(Duration.between(trainItem.getUpdateTime(), now).getSeconds()) <= TrainConstant.STOP_TIME_SECOND) {
+        BigDecimal distance = train.getDistance(nowMillis);
+        if (NumberUtil.isLessOrEqual(distance, BigDecimal.ZERO) && Math.abs(nowMillis - train.getUpdateTimeMillis()) <= TrainConstant.STOP_TIME_SECOND * 1000) {
             // 停车状态 时间不足30S
-            return trainItem;
-        } else if (NumberUtil.isGreater(decimal, BigDecimal.ZERO)) {
-            // 向前行驶计算
-            BigDecimal kilometer = trainItem.getHeadKilometer();
-            for (int i = index; i < selectionAllList.size(); i++) {
-                SelectionEntity entity = selectionAllList.get(i);
-                // if (i == index) {
-                //     decimal = decimal.add(entity.getEndKilometer().subtract(kilometer));
-                // }
-                BigDecimal d = entity.getEndKilometer().subtract(kilometer);
-                if (NumberUtil.isGreaterOrEqual(d, decimal)) {
-                    currentIndex = i;
-                    break;
-                }
-                kilometer = entity.getEndKilometer();
-                decimal = decimal.subtract(d);
+            return train;
+        }
+        if (NumberUtil.isGreater(distance, BigDecimal.ZERO)) {
+            Optional<SectionItem> sectionOptional = StreamUtils.filter(sectionList, item -> item.getSectionId().equals(train.getBlockId())).stream().findFirst();
+            if (sectionOptional.isEmpty()) {
+                throw new IllegalArgumentException(String.format("没有找到当前列车的所在的区段, 区段Name: %s, offset: %s", train.getBlockNo(), train.getOffset()));
             }
-            for (int i = index; i <= currentIndex; i++) {
-                SelectionEntity entity = selectionAllList.get(i);
-                if (stopSignalMap.containsKey(entity.getId())) {
-                    // 期间存在停车标 立即停车
-                    trainItem.setDirection(entity.getDirection());
-                    trainItem.setUpdateTime(now);
-                    trainItem.setSpeed(new BigDecimal("0.00"));
-                    trainItem.setAcceleration(new BigDecimal("0.00"));
-                    BigDecimal kilometerStop = entity.getStartKilometer().add(stopSignalMap.get(entity.getId()).getSelectionOff());
-                    trainItem.setKilometer(kilometerStop);
-                    return trainItem;
+            List<SectionItem> list = this.getSectionByDistance(sectionList, train.getBlockId(), train.getOffset(), distance);
+            log.info("列车预计行驶的区段:{}", list.stream().map(SectionItem::getSectionName).collect(Collectors.joining(",")));
+            // 停车标数据
+            HashMap<Long, SignStopEntity> stopSignalMap = selectionService.getStopSignalMap(list.stream().map(SectionItem::getSectionId).collect(Collectors.toList()));
+            SectionItem currentSection = sectionOptional.get();
+            BigDecimal currentSectionOffset = train.getOffset();
+            BigDecimal currentDistance = distance.abs();
+            for (SectionItem sectionItem : list) {
+                BigDecimal offsetDistance = NumberUtil.add(currentSectionOffset, NumberUtil.mul(sectionItem.getDirection().getFactor(), currentDistance));
+                SignStopEntity stopSign = stopSignalMap.get(sectionItem.getSectionId());
+                if (null != stopSign && NumberUtil.isIn(stopSign.getSelectionOff(), NumberUtil.min(currentSectionOffset, offsetDistance), NumberUtil.max(currentSectionOffset, offsetDistance))) {
+                    // 存在停车标
+                    train.setSpeed(BigDecimal.ZERO);
+                    train.setAcceleration(BigDecimal.ZERO);
+                    train.setBlockId(sectionItem.getSectionId());
+                    train.setBlockNo(sectionItem.getSectionName());
+                    train.setOffset(stopSign.getSelectionOff());
+                    train.setDirection(sectionItem.getDirection());
+                    train.setUpdateTimeMillis(nowMillis);
+                    return train;
                 }
+                currentSection = sectionItem;
+                currentDistance = NumberUtil.add(currentSectionOffset, NumberUtil.mul(sectionItem.getDirection().getFactor(), currentDistance)).abs();
+                currentSectionOffset = BigDecimal.ZERO;
             }
-            SelectionEntity currentSelection = selectionAllList.get(currentIndex);
-            trainItem.setHeadKilometer(now, currentSelection.getStartKilometer().add(decimal), currentSelection);
+            train.setSpeed(CalculatorUtil.velocity(train.getSpeed(), train.getAcceleration(), train.getUpdateTimeMillis(), nowMillis).abs());
+            train.setBlockId(currentSection.getSectionId());
+            train.setBlockNo(currentSection.getSectionName());
+            train.setOffset(currentDistance);
+            train.setDirection(currentSection.getDirection());
+            train.setUpdateTimeMillis(nowMillis);
         }
         // 计算MA
-        BigDecimal distanceMa = new BigDecimal("0.00");
-        for (int i = currentIndex; i < selectionAllList.size(); i++) {
-            SelectionEntity entity = selectionAllList.get(i);
-            if (!stopSignalMap.containsKey(entity.getId())) {
-                distanceMa = distanceMa.add(entity.getSelectionLength());
-                continue;
-            }
-            SignStopEntity signStop = stopSignalMap.get(entity.getId());
-            distanceMa = distanceMa.add(signStop.getSelectionOff());
-            break;
-        }
-        BigDecimal distance = CalculatorUtil.distanceStopLimit(trainItem.getSpeed());
+        BigDecimal distanceMa = this.getDistanceMa(sectionList, train.getBlockId(), train.getOffset());
+        BigDecimal distanceLimit = CalculatorUtil.distanceStopLimit(train.getSpeed());
         // log.info("列车信息:列车:{},速度:{},加速度:{}, 滑行距离:{}", trainItem.getTrainId(), trainItem.getSpeed(), trainItem.getAcceleration(), distance);
-        if (NumberUtil.isLessOrEqual(distanceMa, distance)) {
+        if (NumberUtil.isLessOrEqual(distanceMa, distanceLimit)) {
             // 紧急制动
-            trainItem.setAcceleration(TrainConstant.DECELERATION_LIMIT);
-        } else if (NumberUtil.isGreaterOrEqual(trainItem.getSpeed(), TrainConstant.SPEED_LIMIT_EBI)) {
+            train.setAcceleration(TrainConstant.DECELERATION_LIMIT);
+        } else if (NumberUtil.isGreaterOrEqual(train.getSpeed(), TrainConstant.SPEED_LIMIT_EBI)) {
             // 速度超过限额 制动
-            trainItem.setAcceleration(TrainConstant.DECELERATION_LIMIT);
-        } else if (NumberUtil.isGreaterOrEqual(trainItem.getSpeed(), TrainConstant.SPEED_LIMIT_SBI)) {
+            train.setAcceleration(TrainConstant.DECELERATION_LIMIT);
+        } else if (NumberUtil.isGreaterOrEqual(train.getSpeed(), TrainConstant.SPEED_LIMIT_SBI)) {
             // 速度超过推荐速度 惰性
-            trainItem.setAcceleration(TrainConstant.SLOW_DOWN_LIMIT);
+            train.setAcceleration(TrainConstant.SLOW_DOWN_LIMIT);
         } else {
             // 加速
-            trainItem.setAcceleration(TrainConstant.ACCELERATION_LIMIT);
+            train.setAcceleration(TrainConstant.ACCELERATION_LIMIT);
+        }
+        return train;
+    }
+
+    private BigDecimal getDistanceMa(List<SectionItem> list, Long sectionId, BigDecimal offset) {
+        BigDecimal ma = BigDecimal.ZERO;
+        Optional<SectionItem> sectionItem = list.stream().filter(item -> item.getSectionId().equals(sectionId)).findFirst();
+        if (sectionItem.isPresent()) {
+            SectionItem item = sectionItem.get();
+            ma = ma.add(switch (item.getDirection()) {
+                case UP -> NumberUtil.equals(offset, BigDecimal.ZERO) ? item.getSectionLength().abs() : offset.abs();
+                case DOWN -> NumberUtil.sub(sectionItem.get().getSectionLength(), offset).abs();
+            });
+            ma = ma.add(getDistanceMa(list, sectionItem.get().getSectionNextId(), BigDecimal.ZERO));
+        }
+        return ma;
+    }
+
+    /**
+     * 获取列车心行驶后的区段
+     *
+     * @param list      区段数据
+     * @param sectionId 当前的区段
+     * @param offset    当前偏移量
+     * @param distance  行驶的距离
+     * @return 行驶后所在的区段
+     */
+    private List<SectionItem> getSectionByDistance(LinkedList<SectionItem> list, Long sectionId, BigDecimal offset, BigDecimal distance) {
+        Optional<SectionItem> sectionOptional = StreamUtils.filter(list, item -> item.getSectionId().equals(sectionId)).stream().findFirst();
+        if (sectionOptional.isEmpty()) {
+            throw new IllegalArgumentException(String.format("没有找到当前列车的所在的区段, 区段Id: %s, offset: %s", sectionId, offset));
+        }
+        List<SectionItem> resultList = new ArrayList<>();
+        SectionItem sectionItem = sectionOptional.get();
+        BigDecimal offsetDistance = NumberUtil.add(offset, NumberUtil.mul(distance, sectionItem.getDirection().getFactor()));
+        if (!NumberUtil.isContain(offsetDistance, BigDecimal.ZERO, sectionItem.getSectionLength())) {
+            // 不在当前的区段
+            BigDecimal distanceNext = switch (sectionItem.getDirection()) {
+                case UP -> offsetDistance.abs();
+                case DOWN -> NumberUtil.sub(offsetDistance, sectionItem.getSectionLength());
+            };
+            return getSectionByDistance(list, sectionItem.getSectionNextId(), new BigDecimal("0.00"), distanceNext);
+        } else {
+            resultList.add(sectionItem);
+            return CollUtil.reverse(resultList);
         }
-        return trainItem;
     }
 }

+ 9 - 15
train-server/src/main/java/top/haijunit/train/simulation/train/TrainSimulationHolder.java

@@ -3,8 +3,8 @@ package top.haijunit.train.simulation.train;
 import cn.hutool.core.lang.Assert;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
-import top.haijunit.train.domain.constant.RouteStatusEnum;
-import top.haijunit.train.simulation.domain.RouteItem;
+import top.haijunit.train.domain.constant.SectionStatusEnum;
+import top.haijunit.train.simulation.domain.SectionItem;
 import top.haijunit.train.simulation.domain.TrainItem;
 
 import java.util.Collection;
@@ -23,7 +23,7 @@ public class TrainSimulationHolder {
     // 列车数据共享
     private static final Map<Long, TrainItem> TRAVEL_TRAIN_MAP = new ConcurrentHashMap<>();
     // 进路数据共享
-    private static final Map<Long, LinkedList<RouteItem>> TRAVEL_ROUTE_MAP = new ConcurrentHashMap<>();
+    private static final Map<Long, LinkedList<SectionItem>> TRAVEL_ROUTE_MAP = new ConcurrentHashMap<>();
 
     public static synchronized void addTrainItem(TrainItem trainItem) {
         TRAVEL_TRAIN_MAP.compute(trainItem.getTrainId(), (k, v) -> trainItem);
@@ -33,27 +33,21 @@ public class TrainSimulationHolder {
         return TRAVEL_TRAIN_MAP.values();
     }
 
-    public static synchronized void addRouteList(Long trainId, LinkedList<RouteItem> RouteItemList) {
-        TRAVEL_ROUTE_MAP.compute(trainId, (k, v) -> RouteItemList);
+    public static synchronized void addRouteList(Long trainId, LinkedList<SectionItem> SectionItemList) {
+        TRAVEL_ROUTE_MAP.compute(trainId, (k, v) -> SectionItemList);
     }
 
-    public static LinkedList<RouteItem> getRouteList(Long trainId) {
+    public static LinkedList<SectionItem> getRouteList(Long trainId) {
         return TRAVEL_ROUTE_MAP.getOrDefault(trainId, new LinkedList<>());
     }
 
-    public static Map<Long, LinkedList<RouteItem>> getRouteMap() {
+    public static Map<Long, LinkedList<SectionItem>> getRouteMap() {
         return TRAVEL_ROUTE_MAP;
     }
 
-    public static RouteItem getCurrentRoute(TrainItem train) {
-        Assert.notNull(train, "获取当前列车所在的进路,train:{}", train.getTrainNo());
-        LinkedList<RouteItem> routeList = getRouteList(train.getTrainId());
-        return routeList.stream().filter(item -> item.isContain(train.getHeadKilometer(), train.getDirection())).findFirst().orElse(null);
-    }
-
     public static boolean isRouteIdle(Long routeId) {
-        for (Map.Entry<Long, LinkedList<RouteItem>> entry : TRAVEL_ROUTE_MAP.entrySet()) {
-            return entry.getValue().stream().noneMatch(item -> item.getId().equals(routeId) && !item.getRouteStatus().equals(RouteStatusEnum.IDLE));
+        for (Map.Entry<Long, LinkedList<SectionItem>> entry : TRAVEL_ROUTE_MAP.entrySet()) {
+            return entry.getValue().stream().noneMatch(item -> item.getRouteId().equals(routeId) && !item.getSectionStatus().equals(SectionStatusEnum.IDLE));
         }
         return true;
     }

+ 11 - 6
train-server/src/main/java/top/haijunit/train/utils/CalculatorUtil.java

@@ -1,10 +1,13 @@
 package top.haijunit.train.utils;
 
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
 import lombok.experimental.UtilityClass;
 import top.haijunit.train.domain.constant.TrainConstant;
 
 import java.math.BigDecimal;
 import java.time.Duration;
+import java.time.LocalTime;
 
 /**
  * @author zhanghaijun
@@ -37,13 +40,12 @@ public class CalculatorUtil {
      * @param time         时间
      * @return 距离
      */
-    public static BigDecimal distanceStopLimit(BigDecimal velocity, BigDecimal acceleration, Duration time) {
+    public static BigDecimal distanceStopLimit(BigDecimal velocity, BigDecimal acceleration, long milliseconds) {
         // 初速度为零时,运行距离为零
         if (acceleration.compareTo(BigDecimal.ZERO) == 0) {
             return BigDecimal.ZERO;
         }
-        // 将 Duration 转换为秒数
-        BigDecimal seconds = BigDecimal.valueOf(time.getSeconds()).add(BigDecimal.valueOf(time.getNano(), 9));
+        BigDecimal seconds = NumberUtil.div(new BigDecimal(milliseconds), 1000);
         // 运行距离计算公式
         BigDecimal distance;
         if (NumberUtil.isGreaterOrEqual(acceleration, BigDecimal.ZERO)) {
@@ -64,16 +66,19 @@ public class CalculatorUtil {
      * @param time         时间
      * @return 速度
      */
-    public static BigDecimal velocity(BigDecimal Velocity, BigDecimal acceleration, Duration time) {
+    public static BigDecimal velocity(BigDecimal Velocity, BigDecimal acceleration, BigDecimal seconds) {
         // 如果加速度为零,速度保持不变
         if (acceleration.compareTo(BigDecimal.ZERO) == 0) {
             return Velocity;
         }
-        // 将 Duration 转换为秒数
-        BigDecimal seconds = BigDecimal.valueOf(time.getSeconds());
         // 计算当前速度
         BigDecimal v = Velocity.add(acceleration.multiply(seconds));
         // 将速度限制为零或更大的值
         return v.max(BigDecimal.ZERO);
     }
+
+    public static BigDecimal velocity(BigDecimal velocity, BigDecimal acceleration, Long startMillis, Long endMillis) {
+        BigDecimal seconds = NumberUtil.div(new BigDecimal(Math.abs(startMillis - endMillis)), 1000);
+        return velocity(velocity, acceleration, seconds);
+    }
 }

+ 4 - 4
train-server/src/main/resources/db/changelog/train/db-server-train.xml

@@ -224,8 +224,8 @@
             </column>
             <column name="name" type="VARCHAR(128)" defaultValue="" remarks="车次号"/>
             <column name="train_length" type="DECIMAL(10, 2)" defaultValueNumeric="135.63" remarks="列车长度"/>
-            <column name="init_kilometer" type="DECIMAL(10, 2)" defaultValueNumeric="0" remarks="初始位置公里标"/>
-            <column name="init_direction" type="${number.type}" defaultValueNumeric="2" remarks="初始运行方向"/>
+            <column name="section_id" type="${autoing.type}" defaultValueNumeric="0" remarks="所在的区段"/>
+            <column name="section_offset" type="${number.type}" defaultValueNumeric="2" remarks="区段的偏移量"/>
         </createTable>
     </changeSet>
     <changeSet id="load-db_train-data" author="zhanghaijun" failOnError="false" runOnChange="true">
@@ -234,8 +234,8 @@
             <column name="id" type="NUMERIC"/>
             <column name="name" type="STRING"/>
             <column name="train_length" type="NUMERIC"/>
-            <column name="init_kilometer" type="NUMERIC"/>
-            <column name="init_direction" type="NUMERIC"/>
+            <column name="section_id" type="NUMERIC"/>
+            <column name="section_offset" type="NUMERIC"/>
         </loadUpdateData>
     </changeSet>
 </databaseChangeLog>

+ 6 - 5
train-server/src/main/resources/db/changelog/train/db_train.txt

@@ -1,5 +1,6 @@
-id,name,train_length,init_kilometer,init_direction
-110001,101,135.63,6751.26,1
-110002,103,135.63,3605.63,1
-110003,102,135.63,3731.26,2
-110004,104,135.63,585.63,2
+id,name,train_length,section_id,section_offset
+110001,101,135.63,103009,10
+110002,103,135.63,103007,10
+110003,102,135.63,103020,140
+110004,104,135.63,103018,140
+

+ 33 - 0
train-server/src/test/java/top/haijunit/train/RouteComputerTest.java

@@ -0,0 +1,33 @@
+package top.haijunit.train;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import top.haijunit.train.service.SectionService;
+import top.haijunit.train.simulation.domain.SectionItem;
+
+import java.util.List;
+
+/**
+ * @author zhanghaijun
+ * @date 2023/12/9 11:47
+ * @description [一句话描述该类的功能]
+ */
+@Slf4j
+@SpringBootTest
+public class RouteComputerTest {
+
+    @Autowired
+    private SectionService sectionService;
+
+    @Test
+    void localAllRoute() {
+        List<SectionItem> allSection = sectionService.getAllSection();
+        for (SectionItem item : allSection) {
+            log.info("进路:{},区段名称:{},区段Id:{},上一个区段:{},下一个区段:{}", item.getRouteName(), item.getSectionName(), item.getSectionId(), item.getSectionPreId(), item.getSectionNextId());
+        }
+    }
+}

+ 0 - 31
train-server/src/test/java/top/haijunit/train/test/ConcurrentHashMapTest.java

@@ -1,31 +0,0 @@
-package top.haijunit.train.test;
-
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * @author zhanghaijun
- * @date 2023/12/7 10:59
- * @description [一句话描述该类的功能]
- */
-public class ConcurrentHashMapTest {
-
-    private static ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
-
-    public static void main(String[] args) {
-        // 使用computeIfAbsent计算并插入新值
-        // concurrentHashMap.computeIfAbsent("key", k -> 1);
-        // concurrentHashMap.computeIfAbsent("key", k -> 2);
-
-        // 初始插入
-        // concurrentHashMap.put("key", 1);
-
-        // 使用computeIfPresent更新已存在的值
-        // concurrentHashMap.computeIfPresent("key", (k, v) -> 3);
-
-        concurrentHashMap.compute("key", (k, v) -> 5);
-        concurrentHashMap.compute("key", (k, v) -> 6);
-
-        // 输出更新后的值
-        System.out.println("Value for key 'key': " + concurrentHashMap.get("key"));
-    }
-}