-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from Capstone-Walking/feat/traffic-predictor
Feat/traffic predictor
- Loading branch information
Showing
18 changed files
with
785 additions
and
18 deletions.
There are no files selected for viewing
49 changes: 49 additions & 0 deletions
49
api/src/main/java/com/walking/api/repository/traffic/TrafficDetailRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.walking.api.repository.traffic; | ||
|
||
import com.walking.data.entity.traffic.TrafficDetailEntity; | ||
import com.walking.data.entity.traffic.TrafficEntity; | ||
import java.util.List; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Query; | ||
import org.springframework.data.repository.query.Param; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface TrafficDetailRepository extends JpaRepository<TrafficDetailEntity, Long> { | ||
|
||
/** | ||
* 요청한 각각의 신호등에 대해 최근 start 번째 데이터 ~ end 번째 데이터를 가져옵니다. | ||
* | ||
* @param traffics 데이터를 요청할 신호등 | ||
* @param start 최근 start 번째 데이터 부터 | ||
* @param end end 번째 데이터까지 | ||
* @return 요청한 신호등의 최근 start 번째 데이터 ~ end 번째 데이터 | ||
*/ | ||
@Query( | ||
value = | ||
"WITH sorted_data AS ( " | ||
+ " SELECT *, ROW_NUMBER() OVER (PARTITION BY traffic_id ORDER BY time_left_reg_dt DESC) AS row_num " | ||
+ " FROM traffic_detail " | ||
+ " WHERE traffic_id IN :traffics " | ||
+ " )" | ||
+ " SELECT * " | ||
+ " FROM sorted_data " | ||
+ " WHERE row_num BETWEEN :start AND :end ", | ||
nativeQuery = true) | ||
List<TrafficDetailEntity> findRecentlyData( | ||
@Param("traffics") List<TrafficEntity> traffics, | ||
@Param("start") Integer start, | ||
@Param("end") Integer end); | ||
|
||
@Query( | ||
value = | ||
"SELECT * FROM traffic_detail td " | ||
+ "INNER JOIN (SELECT t.traffic_id, MAX(t.time_left_reg_dt) AS maxTimeLeftRegDt " | ||
+ " FROM traffic_detail t " | ||
+ " WHERE t.traffic_id IN :trafficIds " | ||
+ " GROUP BY t.traffic_id) maxTd " | ||
+ "ON td.traffic_id = maxTd.traffic_id AND td.time_left_reg_dt = maxTd.maxTimeLeftRegDt " | ||
+ "WHERE td.traffic_id IN :trafficIds", | ||
nativeQuery = true) | ||
List<TrafficDetailEntity> findMostRecenlyData(@Param("trafficIds") List<Long> trafficIds); | ||
} |
9 changes: 8 additions & 1 deletion
9
api/src/main/java/com/walking/api/repository/traffic/TrafficRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,15 @@ | ||
package com.walking.api.repository.traffic; | ||
|
||
import com.walking.data.entity.traffic.TrafficEntity; | ||
import java.util.List; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Query; | ||
import org.springframework.data.repository.query.Param; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface TrafficRepository extends JpaRepository<TrafficEntity, Long> {} | ||
public interface TrafficRepository extends JpaRepository<TrafficEntity, Long> { | ||
|
||
@Query("SELECT t FROM TrafficEntity t where t.id IN :ids") | ||
List<TrafficEntity> findByIds(@Param("ids") List<Long> ids); | ||
} |
142 changes: 142 additions & 0 deletions
142
api/src/main/java/com/walking/api/service/TrafficCurrentDetailPredictService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package com.walking.api.service; | ||
|
||
import com.walking.api.repository.traffic.TrafficDetailRepository; | ||
import com.walking.api.service.dto.ColorAndTimeLeft; | ||
import com.walking.api.service.dto.PredictedData; | ||
import com.walking.api.service.dto.request.CurrentDetailRequestDto; | ||
import com.walking.api.service.dto.response.CurrentDetailResponseDto; | ||
import com.walking.api.util.OffsetDateTimeCalculator; | ||
import com.walking.data.entity.traffic.TrafficDetailEntity; | ||
import com.walking.data.entity.traffic.TrafficEntity; | ||
import com.walking.data.entity.traffic.constant.TrafficColor; | ||
import java.time.OffsetDateTime; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class TrafficCurrentDetailPredictService { | ||
|
||
private final TrafficDetailRepository trafficDetailRepository; | ||
|
||
/** | ||
* 예측한 사이클 정보를 바탕으로 현재 신호 색상과 잔여 시간을 계산하여 반환합니다. | ||
* | ||
* @param dto 사이클까지 예측된 데이터를 담은 DTO | ||
* @return 전달 받은 DTO에 존재하는 map에 현재 신호 색상과 잔여 시간 데이터를 마저 채워 반환합니다. | ||
*/ | ||
@Transactional(readOnly = true) | ||
public CurrentDetailResponseDto execute(CurrentDetailRequestDto dto) { | ||
Map<TrafficEntity, PredictedData> predictedMap = dto.getPredictedCycleMap(); | ||
Set<TrafficEntity> traffics = predictedMap.keySet(); | ||
List<Long> trafficIds = | ||
traffics.stream().map(TrafficEntity::getId).collect(Collectors.toList()); | ||
|
||
List<TrafficDetailEntity> mostRecenlyData = | ||
trafficDetailRepository.findMostRecenlyData(trafficIds); | ||
for (TrafficDetailEntity mostRecenlyDatum : mostRecenlyData) { | ||
log.debug( | ||
mostRecenlyDatum.getTraffic().getId() | ||
+ "의 최근 데이터는 (" | ||
+ mostRecenlyDatum.getColor() | ||
+ ", " | ||
+ mostRecenlyDatum.getTimeLeft() | ||
+ ", " | ||
+ mostRecenlyDatum.getTimeLeftRegDt() | ||
+ ")"); | ||
} | ||
Map<TrafficEntity, TrafficDetailEntity> separatedData = separateByTraffic(mostRecenlyData); | ||
|
||
Map<TrafficEntity, ColorAndTimeLeft> currentDetails = new HashMap<>(); | ||
OffsetDateTime now = OffsetDateTime.now(); | ||
for (TrafficEntity traffic : traffics) { | ||
PredictedData predictedData = predictedMap.get(traffic); | ||
if (isUnpredictedData(predictedData)) { // 사이클 예측이 완료되지 않은 데이터 | ||
continue; | ||
} | ||
|
||
TrafficDetailEntity currentTrafficDetailEntity = separatedData.get(traffic); | ||
float differenceInSeconds = | ||
OffsetDateTimeCalculator.getDifferenceInSeconds( | ||
currentTrafficDetailEntity.getTimeLeftRegDt(), now); | ||
|
||
ColorAndTimeLeft currentColorAndtimeLeft = | ||
predictCurrentColorAndTimeLeft( | ||
traffic, currentTrafficDetailEntity, differenceInSeconds, predictedData); | ||
|
||
currentDetails.put(traffic, currentColorAndtimeLeft); | ||
predictedData.updateCurrentColor(currentColorAndtimeLeft.getTrafficColor()); | ||
predictedData.updateCurrentTimeLeft(currentColorAndtimeLeft.getTimeLeft()); | ||
} | ||
return CurrentDetailResponseDto.builder().currentDetails(predictedMap).build(); | ||
} | ||
|
||
/** | ||
* 사이클 예측 과정에서 사이클을 계산하는데 성공한 데이터인지 검증 | ||
* | ||
* @param predictedData | ||
* @return 예측이 실패한 데이터면 true, 아니면 false | ||
*/ | ||
private static boolean isUnpredictedData(PredictedData predictedData) { | ||
return !predictedData.isPredictCycleSuccessful(); | ||
} | ||
|
||
/** | ||
* 하나의 신호등에 대하여 현재 신호 색상과 잔여시간을 예측하는 작업을 수행 | ||
* | ||
* @param traffic 예측할 신호등 | ||
* @param currentTrafficDetailEntity | ||
* @param differenceInSeconds 예측을 해야하는 시간의 크기 | ||
* @param predictedData 예측에 필요한 사이클 정보 | ||
* @return 현재 신호 색상 및 잔여시간 | ||
*/ | ||
private static ColorAndTimeLeft predictCurrentColorAndTimeLeft( | ||
TrafficEntity traffic, | ||
TrafficDetailEntity currentTrafficDetailEntity, | ||
float differenceInSeconds, | ||
PredictedData predictedData) { | ||
ColorAndTimeLeft currentColorAndtimeLeft = | ||
new ColorAndTimeLeft( | ||
currentTrafficDetailEntity.getColor(), currentTrafficDetailEntity.getTimeLeft()); | ||
TrafficColor currentColor = currentTrafficDetailEntity.getColor(); | ||
|
||
// 현재 색상에 대한 잔여시간을 먼저 소진시켜본다. | ||
differenceInSeconds = differenceInSeconds - currentTrafficDetailEntity.getTimeLeft(); | ||
|
||
log.debug("신호등 [" + traffic.getId() + "]의 현재 시간을 예측 하고 있습니다..."); | ||
while (differenceInSeconds >= 0) { | ||
if (currentColor.isRed()) { // 최근 데이터의 색상이 red | ||
currentColor = TrafficColor.GREEN; | ||
Float cycleOfNextColor = predictedData.getCycleByColor(TrafficColor.GREEN); | ||
differenceInSeconds -= cycleOfNextColor; | ||
} else { | ||
currentColor = TrafficColor.RED; | ||
Float cycleOfNextColor = predictedData.getCycleByColor(TrafficColor.RED); | ||
differenceInSeconds -= cycleOfNextColor; | ||
} | ||
} | ||
|
||
currentColorAndtimeLeft.updateColor(currentColor); | ||
currentColorAndtimeLeft.updateTimeLeft(Math.abs(differenceInSeconds)); | ||
return currentColorAndtimeLeft; | ||
} | ||
|
||
private Map<TrafficEntity, TrafficDetailEntity> separateByTraffic( | ||
List<TrafficDetailEntity> mostRecentlyData) { | ||
Map<TrafficEntity, TrafficDetailEntity> separatedData = new HashMap<>(); | ||
|
||
for (TrafficDetailEntity recentlyDatum : mostRecentlyData) { | ||
separatedData.put(recentlyDatum.getTraffic(), recentlyDatum); | ||
} | ||
|
||
return separatedData; | ||
} | ||
} |
Oops, something went wrong.