diff --git a/src/main/java/direction/traffic/simulation/controller/TrafficSimulationController.java b/src/main/java/direction/traffic/simulation/controller/TrafficSimulationController.java index 00fadd7..d420dad 100644 --- a/src/main/java/direction/traffic/simulation/controller/TrafficSimulationController.java +++ b/src/main/java/direction/traffic/simulation/controller/TrafficSimulationController.java @@ -1,6 +1,7 @@ package direction.traffic.simulation.controller; import com.gousade.common.ResponseResult; +import direction.traffic.simulation.entity.CarFlow; import direction.traffic.simulation.entity.TrafficSimulationRunDTO; import direction.traffic.simulation.entity.TrafficSimulationSceneDTO; import direction.traffic.simulation.service.ITrafficSimulationService; @@ -8,12 +9,10 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.validation.Valid; +import java.util.List; @Slf4j @RestController @@ -30,10 +29,24 @@ public ResponseResult init(@Valid @RequestBody TrafficSimulationSceneDTO dto) { return ResponseResult.renderSuccess().data("data", id); } - @ApiOperation(value = "执行仿真") + @ApiOperation(value = "执行仿真, 真实时间") @PostMapping("/run") public ResponseResult run(@Valid @RequestBody TrafficSimulationRunDTO dto) { service.run(dto); return ResponseResult.renderSuccess(); } + + @ApiOperation(value = "执行仿真, 快速模拟返回结果") + @PostMapping("/runFast") + public ResponseResult runFast(@Valid @RequestBody TrafficSimulationRunDTO dto) { + List result = service.runFast(dto); + return ResponseResult.renderSuccess().data("data", result); + } + + @ApiOperation(value = "执行仿真") + @PostMapping("/carFlow/{id}") + public ResponseResult carFlow(@PathVariable("id") Long id) { + List result = service.carFlow(id); + return ResponseResult.renderSuccess().data("data", result); + } } diff --git a/src/main/java/direction/traffic/simulation/entity/CarFlow.java b/src/main/java/direction/traffic/simulation/entity/CarFlow.java index aeb87a3..447f24b 100644 --- a/src/main/java/direction/traffic/simulation/entity/CarFlow.java +++ b/src/main/java/direction/traffic/simulation/entity/CarFlow.java @@ -3,13 +3,20 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; +@AllArgsConstructor +@NoArgsConstructor @Data public class CarFlow implements Serializable { + public static final String CAR = "car"; + public static final String TRUCK = "truck"; + @ApiModelProperty("车辆id") @JsonSerialize(using = ToStringSerializer.class) private Long id; @@ -17,6 +24,7 @@ public class CarFlow implements Serializable { private BigDecimal expectSpeed; private BigDecimal realSpeed; + @ApiModelProperty("车辆类型,car客车 truck货车 客车:长4.5m,宽1.7米 货车:长7.2m,宽2.3米") private String carType; diff --git a/src/main/java/direction/traffic/simulation/entity/CarLocation.java b/src/main/java/direction/traffic/simulation/entity/CarLocation.java index 37b3b17..7c4606a 100644 --- a/src/main/java/direction/traffic/simulation/entity/CarLocation.java +++ b/src/main/java/direction/traffic/simulation/entity/CarLocation.java @@ -1,11 +1,16 @@ package direction.traffic.simulation.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; +@AllArgsConstructor +@NoArgsConstructor @Data public class CarLocation implements Serializable { private String roadId; @@ -16,4 +21,18 @@ public class CarLocation implements Serializable { private int laneOffset; @ApiModelProperty("变道需要3秒,需要变道时将这个字段赋值为300,每次移动减少该值") private int changeLaneNeedTimes; + + /** + * 这个字段的作用是当前方压车但还不具备变道条件时, 需要减速保持安全距离, 但还想尝试变道时设置为true + * 下次计算时因为压车在安全距离, 但还是尝试进行变道 + */ + @JsonIgnore + @ApiModelProperty("是否期望变道") + private boolean wantChaneLane; + + public CarLocation(String roadId, BigDecimal position, int lane) { + this.roadId = roadId; + this.position = position; + this.lane = lane; + } } diff --git a/src/main/java/direction/traffic/simulation/entity/Road.java b/src/main/java/direction/traffic/simulation/entity/Road.java index 1cd35ad..30e599d 100644 --- a/src/main/java/direction/traffic/simulation/entity/Road.java +++ b/src/main/java/direction/traffic/simulation/entity/Road.java @@ -3,12 +3,15 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; import lombok.Data; import java.io.Serializable; import java.math.BigDecimal; import java.util.List; + +@Builder @Data public class Road implements Serializable { @JsonSerialize(using = ToStringSerializer.class) diff --git a/src/main/java/direction/traffic/simulation/entity/RoadLimitSpeed.java b/src/main/java/direction/traffic/simulation/entity/RoadLimitSpeed.java index 680c72b..7972c16 100644 --- a/src/main/java/direction/traffic/simulation/entity/RoadLimitSpeed.java +++ b/src/main/java/direction/traffic/simulation/entity/RoadLimitSpeed.java @@ -1,10 +1,14 @@ package direction.traffic.simulation.entity; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; +@AllArgsConstructor +@NoArgsConstructor @Data public class RoadLimitSpeed implements Serializable { private BigDecimal beginPosition; diff --git a/src/main/java/direction/traffic/simulation/entity/TrafficSimulationSceneDTO.java b/src/main/java/direction/traffic/simulation/entity/TrafficSimulationSceneDTO.java index e550e67..7c8fd82 100644 --- a/src/main/java/direction/traffic/simulation/entity/TrafficSimulationSceneDTO.java +++ b/src/main/java/direction/traffic/simulation/entity/TrafficSimulationSceneDTO.java @@ -3,13 +3,17 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.io.Serializable; import java.util.List; +@AllArgsConstructor +@NoArgsConstructor @Data public class TrafficSimulationSceneDTO implements Serializable { @JsonSerialize(using = ToStringSerializer.class) diff --git a/src/main/java/direction/traffic/simulation/service/ITrafficSimulationService.java b/src/main/java/direction/traffic/simulation/service/ITrafficSimulationService.java index c69fc85..e72a210 100644 --- a/src/main/java/direction/traffic/simulation/service/ITrafficSimulationService.java +++ b/src/main/java/direction/traffic/simulation/service/ITrafficSimulationService.java @@ -1,8 +1,11 @@ package direction.traffic.simulation.service; +import direction.traffic.simulation.entity.CarFlow; import direction.traffic.simulation.entity.TrafficSimulationRunDTO; import direction.traffic.simulation.entity.TrafficSimulationSceneDTO; +import java.util.List; + public interface ITrafficSimulationService { String init(TrafficSimulationSceneDTO dto); @@ -12,4 +15,8 @@ public interface ITrafficSimulationService { * @param dto */ void run(TrafficSimulationRunDTO dto); + + List runFast(TrafficSimulationRunDTO dto); + + List carFlow(Long id); } diff --git a/src/main/java/direction/traffic/simulation/service/impl/TrafficSimulationServiceImpl.java b/src/main/java/direction/traffic/simulation/service/impl/TrafficSimulationServiceImpl.java index ce1800f..0ae1ec3 100644 --- a/src/main/java/direction/traffic/simulation/service/impl/TrafficSimulationServiceImpl.java +++ b/src/main/java/direction/traffic/simulation/service/impl/TrafficSimulationServiceImpl.java @@ -1,12 +1,14 @@ package direction.traffic.simulation.service.impl; import com.github.yitter.idgen.YitIdHelper; +import direction.traffic.simulation.entity.CarFlow; import direction.traffic.simulation.entity.TrafficSimulationRunDTO; import direction.traffic.simulation.entity.TrafficSimulationSceneDTO; import direction.traffic.simulation.service.ITrafficSimulationService; import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Objects; import java.util.concurrent.*; @@ -33,10 +35,36 @@ public void run(TrafficSimulationRunDTO dto) { "交通流仿真-场景id不存在,请先添加场景再运行。"); // 判断锁 避免多线程同时对一个场景进行模拟 这里没加redis 所以用内存锁 实际应用可以修改为redisUtils.setnx(id, dto.getMillis())锁校验 boolean lock = thread.getLock().isLocked(); - if (!lock) { + if (lock) { throw new RuntimeException("场景已经在运行中,请等待运行结束再重新请求。"); } thread.setRun(dto); - THREAD_POOL.execute(thread); + THREAD_POOL.submit(thread); + } + + @Override + public List runFast(TrafficSimulationRunDTO dto) { + TrafficSimulationThread thread = Objects.requireNonNull(map.get(dto.getId()), + "交通流仿真-场景id不存在,请先添加场景再运行。"); + // 判断锁 避免多线程同时对一个场景进行模拟 这里没加redis 所以用内存锁 实际应用可以修改为redisUtils.setnx(id, dto.getMillis())锁校验 + boolean lock = thread.getLock().isLocked(); + if (lock) { + throw new RuntimeException("场景已经在运行中,请等待运行结束再重新请求。"); + } + dto.setRunFast(true); + thread.setRun(dto); + Future> future = THREAD_POOL.submit(thread); + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public List carFlow(Long id) { + TrafficSimulationThread thread = Objects.requireNonNull(map.get(id), + "交通流仿真-场景id不存在,请先添加场景再查询。"); + return thread.getResult(); } } diff --git a/src/main/java/direction/traffic/simulation/service/impl/TrafficSimulationThread.java b/src/main/java/direction/traffic/simulation/service/impl/TrafficSimulationThread.java index 5cbe952..0783dfc 100644 --- a/src/main/java/direction/traffic/simulation/service/impl/TrafficSimulationThread.java +++ b/src/main/java/direction/traffic/simulation/service/impl/TrafficSimulationThread.java @@ -8,14 +8,17 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.stream.Collectors; +import static direction.traffic.simulation.entity.CarFlow.CAR; + @Setter @Getter -public class TrafficSimulationThread implements Runnable { +public class TrafficSimulationThread implements Callable> { private final ReentrantLock lock = new ReentrantLock(); private TrafficSimulationSceneDTO dto; private TrafficSimulationRunDTO run; @@ -36,7 +39,7 @@ public TrafficSimulationThread(TrafficSimulationSceneDTO dto) { } @Override - public void run() { + public List call() { try { lock.lock(); result = carFlows.stream().map(e -> { @@ -57,8 +60,18 @@ public void run() { runOnce(); // 一次运行完之后修正变道相关信息 getResult().forEach(e -> { + int oldChangeLaneNeedTimes = e.getLocation().getChangeLaneNeedTimes(); + if (oldChangeLaneNeedTimes <= 0) { + return; + } e.getLocation().setChangeLaneNeedTimes(Math.max(0, e.getLocation().getChangeLaneNeedTimes() - multipleSpeed)); - e.getLocation().setLaneOffset(e.getLocation().getChangeLaneNeedTimes() == 0 ? 0 : e.getLocation().getLaneOffset()); + // 变道完成后恢复期望速度 + if (e.getLocation().getChangeLaneNeedTimes() == 0) { + e.setRealSpeed(e.getExpectSpeed()); + e.getLocation().setLane(e.getLocation().getLane() + e.getLocation().getLaneOffset()); + e.getLocation().setLaneOffset(0); + e.getLocation().setWantChaneLane(false); + } }); } if (!runFast) { @@ -69,13 +82,14 @@ public void run() { } } } + return this.result; } finally { lock.unlock(); } } private void runOnce() { - Map> groups = carFlows.stream().collect(Collectors.groupingBy(e -> e.getLocation().getRoadId())); + Map> groups = result.stream().collect(Collectors.groupingBy(e -> e.getLocation().getRoadId())); groups.forEach((k, v) -> { Road road = roadMap.get(k); v.sort(Comparator.comparing(o -> o.getLocation().getPosition(), Comparator.reverseOrder())); @@ -110,10 +124,13 @@ private void runOnce() { CarFlow lastCar = laneCarList.getLast(); boolean safe = exceptLocation.add(buildCarLength(e.getCarType())).add(BigDecimal.valueOf(0.02)) .compareTo(lastCar.getLocation().getPosition().subtract(buildCarLength(e.getCarType()))) < 0; - if (safe) { + if (safe && !e.getLocation().isWantChaneLane()) { + e.getLocation().setPosition(exceptLocation); laneCarList.addLast(e); return; } + // 压车且不具备变道条件时设置为true,可以在下次计算时直接尝试变道 + e.getLocation().setWantChaneLane(true); boolean changeLane = tryChangeLane(originalMap, e); if (changeLane) { // 3秒除以10毫秒 @@ -136,8 +153,8 @@ private void runOnce() { private boolean tryChangeLane(Map> originalMap, CarFlow e) { int lane = e.getLocation().getLane(); - LinkedList list1 = originalMap.getOrDefault(lane - 1, new LinkedList<>()); - LinkedList list2 = originalMap.getOrDefault(lane + 1, new LinkedList<>()); + LinkedList list1 = originalMap.get(lane - 1); + LinkedList list2 = originalMap.get(lane + 1); boolean canChangeLeftLane = checkCanChangeLane(list1, e); if (canChangeLeftLane) { e.getLocation().setLaneOffset(-1); @@ -152,6 +169,10 @@ private boolean tryChangeLane(Map> originalMap, Car } private boolean checkCanChangeLane(LinkedList list, CarFlow e) { + // 为null说明根本没有这条车道 直接false + if (list == null) { + return false; + } BigDecimal position = e.getLocation().getPosition(); // 车辆不多直接allMatch 数量多可以改为取前车最后一个和后车第一个比较 boolean frontMatch = list.stream().filter(o -> o.getLocation().getPosition().compareTo(position) > 0) @@ -168,7 +189,7 @@ private BigDecimal buildCarLength(CarFlow e) { } private BigDecimal buildCarLength(String carType) { - if ("car".equals(carType)) { + if (CAR.equals(carType)) { return BigDecimal.valueOf(0.0045 / 2); } else { return BigDecimal.valueOf(0.0072 / 2); @@ -199,6 +220,6 @@ private BigDecimal buildExceptLocation(Road road, CarFlow car) { } private BigDecimal buildDistance(CarFlow e) { - return e.getRealSpeed()/*.multiply(BigDecimal.valueOf(10))*/.divide(BigDecimal.valueOf(360000), 3, RoundingMode.HALF_UP); + return e.getRealSpeed()/*.multiply(BigDecimal.valueOf(10))*/.divide(BigDecimal.valueOf(360000), 20, RoundingMode.HALF_UP); } } diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index f2cba9b..1f982e3 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -163,6 +163,22 @@ --> + + + + + + + + + + + + + diff --git a/src/test/java/com/gousade/TrafficSimulationTests.java b/src/test/java/com/gousade/TrafficSimulationTests.java new file mode 100644 index 0000000..d18fc38 --- /dev/null +++ b/src/test/java/com/gousade/TrafficSimulationTests.java @@ -0,0 +1,116 @@ +package com.gousade; + +import direction.traffic.simulation.entity.*; +import direction.traffic.simulation.service.ITrafficSimulationService; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static direction.traffic.simulation.entity.CarFlow.CAR; +import static direction.traffic.simulation.entity.CarFlow.TRUCK; + +/** + * 测试类包名需要在主程序包名下,否则自动注入有点问题 + */ +@Slf4j +@SpringBootTest +public class TrafficSimulationTests { + @Autowired + private ITrafficSimulationService service; + + private List roadSegments; + + @BeforeEach + public void setUp() { + Road roadA = Road.builder().id("001").name("路段A").beginPosition(BigDecimal.valueOf(1.100)) + .endPosition(BigDecimal.valueOf(50.000)) + .laneNum(1) + .limitSpeeds(Arrays.asList(new RoadLimitSpeed(BigDecimal.valueOf(1.100), BigDecimal.valueOf(50.000), BigDecimal.valueOf(120)), + new RoadLimitSpeed(BigDecimal.valueOf(23.500), BigDecimal.valueOf(25.500), BigDecimal.valueOf(80)))) + .build(); + Road roadB = Road.builder().id("002").name("路段B").beginPosition(BigDecimal.valueOf(50.000)) + .endPosition(BigDecimal.valueOf(100.000)) + .laneNum(2) + .limitSpeeds(Arrays.asList(new RoadLimitSpeed(BigDecimal.valueOf(50), BigDecimal.valueOf(100), BigDecimal.valueOf(120)), + new RoadLimitSpeed(BigDecimal.valueOf(80.500), BigDecimal.valueOf(85.500), BigDecimal.valueOf(80)))) + .build(); + roadSegments = Arrays.asList(roadA, roadB); + } + + @Test + public void test1() { + CarFlow car1 = new CarFlow(1L, BigDecimal.valueOf(110), null, CAR, new CarLocation("001", BigDecimal.valueOf(2.452), 1)); + Map map = run(Collections.singletonList(car1), 2L * 60 * 1000); + CarFlow car = map.get(1L); + // 由于每次计算距离时并不是整数 会四舍五入,所以这里无法用Assert去断言一个确定结果值 + log.info("小客车1分钟后位置{}, 速度{}, 车道{}", car.getLocation().getPosition(), car.getRealSpeed(), car.getLocation().getLane()); + // 结果应该近似为6.119 + } + + public Map run(List carFlows, Long millis) { + TrafficSimulationSceneDTO dto = new TrafficSimulationSceneDTO(null, roadSegments, carFlows); + String id = service.init(dto); + TrafficSimulationRunDTO runDTO = new TrafficSimulationRunDTO(); + runDTO.setId(Long.valueOf(id)); + runDTO.setMillis(millis); + return service.runFast(runDTO).stream().collect(Collectors.toMap(CarFlow::getId, Function.identity())); + } + + + @Test + public void test2() { + CarFlow car1 = new CarFlow(1L, BigDecimal.valueOf(110), null, CAR, new CarLocation("001", BigDecimal.valueOf(22.500), 1)); + Map map = run(Collections.singletonList(car1), 2L * 60 * 1000); + CarFlow car = map.get(1L); + log.info("小客车1分钟后位置{}, 速度{}, 车道{}", car.getLocation().getPosition(), car.getRealSpeed(), car.getLocation().getLane()); + } + + @Test + public void test3() { + CarFlow car1 = new CarFlow(1L, BigDecimal.valueOf(75), null, TRUCK, new CarLocation("001", BigDecimal.valueOf(2.700), 1)); + CarFlow car2 = new CarFlow(2L, BigDecimal.valueOf(110), null, CAR, new CarLocation("001", BigDecimal.valueOf(2.000), 1)); + Map map = run(Arrays.asList(car1, car2), 1L * 60 * 1000); + CarFlow car = map.get(2L); + log.info("小客车1分钟后位置{}, 速度{}, 车道{}", car.getLocation().getPosition(), car.getRealSpeed(), car.getLocation().getLane()); + Map map2 = run(Arrays.asList(car1, car2), 2L * 60 * 1000); + CarFlow c2 = map2.get(2L); + log.info("小客车2分钟后位置{}, 速度{}, 车道{}", c2.getLocation().getPosition(), c2.getRealSpeed(), c2.getLocation().getLane()); + } + + @Test + public void test4() { + CarFlow car1 = new CarFlow(1L, BigDecimal.valueOf(75), null, TRUCK, new CarLocation("002", BigDecimal.valueOf(52.700), 1)); + CarFlow car2 = new CarFlow(2L, BigDecimal.valueOf(110), null, CAR, new CarLocation("002", BigDecimal.valueOf(52.000), 1)); + Map map = run(Arrays.asList(car1, car2), 1L * 60 * 1000); + CarFlow car = map.get(2L); + log.info("小客车1分钟后位置{}, 速度{}, 车道{}", car.getLocation().getPosition(), car.getRealSpeed(), car.getLocation().getLane()); + Map map2 = run(Arrays.asList(car1, car2), 2L * 60 * 1000); + CarFlow c2 = map2.get(2L); + log.info("小客车2分钟后位置{}, 速度{}, 车道{}", c2.getLocation().getPosition(), c2.getRealSpeed(), c2.getLocation().getLane()); + } + + @Test + public void test5() { + CarFlow car1 = new CarFlow(1L, BigDecimal.valueOf(110), null, CAR, new CarLocation("002", BigDecimal.valueOf(51.100), 2)); + CarFlow car2 = new CarFlow(2L, BigDecimal.valueOf(110), null, CAR, new CarLocation("002", BigDecimal.valueOf(51.071), 1)); + CarFlow car3 = new CarFlow(3L, BigDecimal.valueOf(110), null, CAR, new CarLocation("002", BigDecimal.valueOf(51.115), 1)); + CarFlow car4 = new CarFlow(4L, BigDecimal.valueOf(110), null, CAR, new CarLocation("002", BigDecimal.valueOf(51.000), 1)); + CarFlow car5 = new CarFlow(5L, BigDecimal.valueOf(75), null, TRUCK, new CarLocation("002", BigDecimal.valueOf(51.700), 2)); + Map map = run(Arrays.asList(car1, car2, car3, car4, car5), 1L * 60 * 1000); + CarFlow car = map.get(1L); + log.info("小客车1分钟后位置{}, 速度{}, 车道{}", car.getLocation().getPosition(), car.getRealSpeed(), car.getLocation().getLane()); + Map map2 = run(Arrays.asList(car1, car2, car3, car4, car5), 2L * 60 * 1000); + CarFlow c2 = map2.get(1L); + log.info("小客车2分钟后位置{}, 速度{}, 车道{}", c2.getLocation().getPosition(), c2.getRealSpeed(), c2.getLocation().getLane()); + } +}