Skip to content

Commit

Permalink
fix: 交通流模拟问题完善,添加测试代码
Browse files Browse the repository at this point in the history
  • Loading branch information
leafLeaf9 committed Feb 24, 2024
1 parent 514f80c commit 212379a
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
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;
import io.swagger.annotations.ApiOperation;
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
Expand All @@ -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<CarFlow> result = service.runFast(dto);
return ResponseResult.renderSuccess().data("data", result);
}

@ApiOperation(value = "执行仿真")
@PostMapping("/carFlow/{id}")
public ResponseResult carFlow(@PathVariable("id") Long id) {
List<CarFlow> result = service.carFlow(id);
return ResponseResult.renderSuccess().data("data", result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@
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;

private BigDecimal expectSpeed;
private BigDecimal realSpeed;


@ApiModelProperty("车辆类型,car客车 truck货车 客车:长4.5m,宽1.7米 货车:长7.2m,宽2.3米")
private String carType;

Expand Down
19 changes: 19 additions & 0 deletions src/main/java/direction/traffic/simulation/entity/CarLocation.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
}
3 changes: 3 additions & 0 deletions src/main/java/direction/traffic/simulation/entity/Road.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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);

Expand All @@ -12,4 +15,8 @@ public interface ITrafficSimulationService {
* @param dto
*/
void run(TrafficSimulationRunDTO dto);

List<CarFlow> runFast(TrafficSimulationRunDTO dto);

List<CarFlow> carFlow(Long id);
}
Original file line number Diff line number Diff line change
@@ -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.*;

Expand All @@ -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<CarFlow> 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<List<CarFlow>> future = THREAD_POOL.submit(thread);
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}

@Override
public List<CarFlow> carFlow(Long id) {
TrafficSimulationThread thread = Objects.requireNonNull(map.get(id),
"交通流仿真-场景id不存在,请先添加场景再查询。");
return thread.getResult();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<List<CarFlow>> {
private final ReentrantLock lock = new ReentrantLock();
private TrafficSimulationSceneDTO dto;
private TrafficSimulationRunDTO run;
Expand All @@ -36,7 +39,7 @@ public TrafficSimulationThread(TrafficSimulationSceneDTO dto) {
}

@Override
public void run() {
public List<CarFlow> call() {
try {
lock.lock();
result = carFlows.stream().map(e -> {
Expand All @@ -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) {
Expand All @@ -69,13 +82,14 @@ public void run() {
}
}
}
return this.result;
} finally {
lock.unlock();
}
}

private void runOnce() {
Map<String, List<CarFlow>> groups = carFlows.stream().collect(Collectors.groupingBy(e -> e.getLocation().getRoadId()));
Map<String, List<CarFlow>> 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()));
Expand Down Expand Up @@ -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毫秒
Expand All @@ -136,8 +153,8 @@ private void runOnce() {

private boolean tryChangeLane(Map<Integer, LinkedList<CarFlow>> originalMap, CarFlow e) {
int lane = e.getLocation().getLane();
LinkedList<CarFlow> list1 = originalMap.getOrDefault(lane - 1, new LinkedList<>());
LinkedList<CarFlow> list2 = originalMap.getOrDefault(lane + 1, new LinkedList<>());
LinkedList<CarFlow> list1 = originalMap.get(lane - 1);
LinkedList<CarFlow> list2 = originalMap.get(lane + 1);
boolean canChangeLeftLane = checkCanChangeLane(list1, e);
if (canChangeLeftLane) {
e.getLocation().setLaneOffset(-1);
Expand All @@ -152,6 +169,10 @@ private boolean tryChangeLane(Map<Integer, LinkedList<CarFlow>> originalMap, Car
}

private boolean checkCanChangeLane(LinkedList<CarFlow> 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)
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
}
16 changes: 16 additions & 0 deletions src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,22 @@
<appender-ref ref="ConsoleInfoLog"/> <appender-ref ref="FileInfoLog"/> <appender-ref
ref="FileWarnLog"/> <appender-ref ref="FileErrorLog"/> </logger> -->

<springProfile name="default">
<!-- <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。 <logger>仅有一个name属性,
一个可选的level和一个可选的addtivity属性。 name:用来指定受此logger约束的某一个包或者具体的某一个类。 level:用来设置打印级别,大小写无关:TRACE,
DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 如果未设置此属性,那么当前logger将会继承上级的级别。 -->
<!--可以输出项目中的debug日志,包括mybatis的sql日志 -->
<logger name="com.gousade.mapper" level="debug"/>

<!-- root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性 level:用来设置打印级别,大小写无关:TRACE,
DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG 可以包含零个或多个appender元素。 -->
<root level="INFO">
<appender-ref ref="ConsoleInfoLog"/>
<appender-ref ref="FileInfoLog"/>
<appender-ref ref="FileWarnLog"/>
<appender-ref ref="FileErrorLog"/>
</root>
</springProfile>

<!--开发环境 -->
<springProfile name="dev">
Expand Down
Loading

0 comments on commit 212379a

Please sign in to comment.