신호생성 repo (24. 1. 5 ~).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

587 lines
33 KiB

import pandas as pd
import numpy as np
import os, sys, traci
import json
import sumolib
from tqdm import tqdm
class DailyPreprocessor():
def __init__(self):
self.path_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.issues = []
# 1. 데이터 불러오기
def load_data(self):
self.load_networks()
self.load_tables()
self.check_networks()
self.check_tables()
print('1. 모든 데이터가 로드되었습니다.')
# 1-1. 네트워크 불러오기
def load_networks(self):
self.net = sumolib.net.readNet(os.path.join(self.path_root, 'Data', 'networks', 'sn.net.xml'))
print("1-1. 네트워크가 로드되었습니다.")
# 1-2. 테이블 불러오기
def load_tables(self):
# 모든 컬럼에 대하여 데이터타입 지정
loading_dtype = {
'inter_no':'int', 'start_hour':'int', 'start_minute':'int', 'cycle':'int','offset':'int',
'node_id':'str', 'inter_type':'str', 'parent_id':'str','child_id':'str',
'direction':'str', 'condition':'str', 'inc_edge':'str', 'out_edge':'str',
'end_unix':'int', 'inter_name':'str', 'inter_lat':'float', 'inter_lon':'float',
'group_no':'int', 'main_phase_no':'int', 'phase_no':'int','ring_type':'str'
}
for alph in ['A', 'B']:
for j in range(1,9):
loading_dtype[f'angle_{alph}{j}'] = 'str'
loading_dtype[f'dura_{alph}{j}'] = 'int'
self.path_table = os.path.join(self.path_root, 'Data', 'tables')
self.inter_info = pd.read_csv(os.path.join(self.path_table, 'inter_info.csv'), dtype=loading_dtype)
self.angle = pd.read_csv(os.path.join(self.path_table, 'angle.csv'), dtype=loading_dtype)
self.plan = pd.read_csv(os.path.join(self.path_table, 'plan.csv'), dtype=loading_dtype)
self.inter_node = pd.read_csv(os.path.join(self.path_table, 'inter_node.csv'), dtype=loading_dtype)
self.uturn = pd.read_csv(os.path.join(self.path_table, 'child_uturn.csv'), dtype=loading_dtype)
self.coord = pd.read_csv(os.path.join(self.path_table, 'child_coord.csv'), dtype=loading_dtype)
self.nema = pd.read_csv(os.path.join(self.path_table, 'nema.csv'), encoding='cp949', dtype=loading_dtype)
print("1-2. 테이블들이 로드되었습니다.")
# 1-3. 테이블 불러오기
def check_networks(self):
# https://sumo.dlr.de/docs/Netedit/neteditUsageExamples.html#simplify_tls_program_state_after_changing_connections
if 'SUMO_HOME' in os.environ:
tools = os.path.join(os.environ['SUMO_HOME'], 'tools')
if tools not in sys.path:
sys.path.append(tools)
else:
raise EnvironmentError("please declare environment variable 'SUMO_HOME'")
traci.start([sumolib.checkBinary('sumo'), "-n", os.path.join(self.path_root, 'Data', 'networks', 'sn.net.xml')])
nodes = [node for node in self.net.getNodes() if node.getType()=='traffic_light']
for node in nodes:
node_id = node.getID()
from_xml = len([c for c in node.getConnections() if c.getTLLinkIndex() >= 0])
from_traci = len(traci.trafficlight.getRedYellowGreenState(node_id))
if from_xml != from_traci:
sub = {'id': node_id, 'type': 'node', 'note': '유효하지 않은 연결이있음. netedit에서 clean states 필요.'}
self.issues.append(sub)
traci.close()
print("1-3. 네트워크의 모든 clean state requirement들을 체크했습니다.")
# 1-4. 테이블의 무결성 검사
def check_tables(self):
self.check_inter_info()
self.check_angle()
self.check_plan()
print("1-4. 모든 테이블들의 무결성을 검사했고 이상 없습니다.")
pass
# 1-4-1. 교차로정보(inter_info) 검사
def check_inter_info(self):
# 1-4-1-1. inter_lat, inter_lon 적절성 검사
self.inter_info.loc[0, 'inter_lat'] = 38.0 # 에러 발생을 위한 코드
self.max_lon, self.min_lon = 127.207888, 127.012492
self.max_lat, self.min_lat = 37.480693, 37.337112
for _, row in self.inter_info.iterrows():
latbool = self.min_lat <= row['inter_lat'] <= self.max_lat
lonbool = self.min_lon <= row['inter_lon'] <= self.max_lon
if not(latbool and lonbool):
msg = f"1-4-1-1. 위도 또는 경도가 범위를 벗어난 교차로가 있습니다: inter_no : {row['inter_no']}"
self.issues.append(msg)
# 교차로목록 정의
self.inter_nos = sorted(self.inter_info.inter_no.unique())
# 1-4-2. 방위각정보(inter_info) 검사
def check_angle(self):
# 1-4-2-1. inter_no 검사
# self.angle.loc[0, 'inter_no'] = '4' # 에러 발생을 위한 코드
missing_inter_nos = set(self.angle.inter_no) - set(self.inter_nos)
if missing_inter_nos:
msg = f"1-4-2-1. angle의 inter_no 중 교차로 목록(inter_nos)에 포함되지 않는 항목이 있습니다: {missing_inter_nos}"
self.issues.append(msg)
# 1-4-3. 신호계획(plan) 검사
def check_plan(self):
# 1-4-3-1. inter_no 검사
# self.plan.loc[0, 'inter_no'] = '4' # 에러 발생을 위한 코드
missing_inter_nos = set(self.plan.inter_no) - set(self.inter_nos)
if missing_inter_nos:
msg = f"1-4-3-1. plan의 inter_no 중 교차로 목록(inter_nos)에 포함되지 않는 항목이 있습니다: {missing_inter_nos}"
self.issues.append(msg)
# 1-4-3-2. 시작시각 검사
# self.plan.loc[0, 'start_hour'] = 27 # 에러 발생을 위한 코드
for _, row in self.plan.iterrows():
start_hour = row.start_hour
start_minute = row.start_minute
if not (0 <= start_hour <= 23) or not (0 <= start_minute <= 59):
msg = f"1-4-3-2. plan에 잘못된 형식의 start_time이 존재합니다: {start_hour, start_minute}"
self.issues.append(msg)
# 1-4-3-3. 현시시간 검사
# self.plan.loc[0, 'dura_A1'] = -2 # 에러 발생을 위한 코드
durations = self.plan[[f'dura_{alph}{j}' for alph in ['A','B'] for j in range(1, 9)]]
valid_indices = ((durations >= 0) & (durations <= 200)).all(axis=1)
invalid_inter_nos = sorted(self.plan[~ valid_indices].inter_no.unique())
if invalid_inter_nos:
msg = f"1-4-3-3. 음수이거나 200보다 큰 현시시간이 존재합니다. : {invalid_inter_nos}"
# 1-4-3-4. 주기 일관성 검사
# self.plan.loc[0, 'cycle'] = 50 # 에러 발생을 위한 코드
inconsistent_cycle = self.plan.groupby(['inter_no', 'start_hour', 'start_minute'])['cycle'].nunique().gt(1)
if inconsistent_cycle.any():
inc_inter_no, start_hour, start_minute = inconsistent_cycle[inconsistent_cycle].index[0]
msg = f"1-4-3-4. inter_no:{inc_inter_no}, start_hour:{start_minute}, start_hour:{start_minute}일 때, cycle이 유일하게 결정되지 않습니다."
self.issues.append(msg)
# 1-4-3-5. 현시시간 / 주기 검사
# self.plan.loc[0, 'duration'] = 10 # 에러 발생을 위한 코드
right_duration = True
for (inter_no, start_hour, start_minute), group in self.plan.groupby(['inter_no', 'start_hour', 'start_minute']):
A_sum = group[[f'dura_A{j}' for j in range(1, 9)]].iloc[0].sum()
B_sum = group[[f'dura_B{j}' for j in range(1, 9)]].iloc[0].sum()
# A_sum = group[group['ring_type']=='A']['duration'].sum()
# B_sum = group[group['ring_type']=='B']['duration'].sum()
cycle = group['cycle'].unique()[0]
if not (A_sum == B_sum == cycle):
right_duration = False
inc_inter_no = inter_no
if not right_duration:
msg = f"1-4-4-5. inter_no:{inc_inter_no}, A링현시시간의 합과 B링현시시간의 합이 일치하지 않거나, 현시시간의 합과 주기가 일치하지 않습니다."
self.issues.append(msg)
# 2. 중간산출물 만들기
def get_intermediates(self):
self.get_matches()
# self.get_movements()
self.get_node2num_cycles()
# 2-1 매칭테이블들 생성
def get_matches(self):
self.make_match1()
self.make_match2()
self.make_match3()
self.make_match4()
self.make_match5()
self.make_match6()
self.make_matching()
# 2-1-1
def make_match1(self):
'''
신호 DB에는 매 초마다 이동류정보가 업데이트 된다. 그리고 이 이동류정보를 매 5초마다 불러와서 사용하게 된다.
'../Data/tables/move/'에는 5초마다의 이동류정보가 저장되어 있다.
return : 통합된 이동류정보
- 모든 inter_no(교차로번호)에 대한 A, B링 현시별 이동류정보
match1을 만드는 데 시간이 소요되므로 한 번 만들어서 저장해두고 저장해둔 것을 쓴다.
'''
# [이동류번호] 불러오기 (약 1분의 소요시간)
path_move = os.path.join(self.path_root, 'Data', 'tables', 'move')
csv_moves = os.listdir(path_move)
moves = [pd.read_csv(os.path.join(path_move, csv_move), index_col=0) for csv_move in tqdm(csv_moves, desc='이동류정보 불러오는 중 : match1')]
self.match1 = pd.concat(moves).drop_duplicates().sort_values(by=['inter_no','phas_A','phas_B']).reset_index(drop=True)
self.match1.to_csv(os.path.join(self.path_root, 'Intermediates', 'match1.csv'))
# 2-1-2
def make_match2(self):
'''
match1을 계층화함.
- match1의 컬럼 : inter_no, phas_A, phas_B, move_A, move_B
- match2의 컬럼 : inter_no, phase_no, ring_type, move_no
'''
# 계층화 (inter_no, phas_A, phas_B, move_A, move_B) -> ('inter_no', 'phase_no', 'ring_type', 'move_no')
matchA = self.match1[['inter_no', 'phas_A', 'move_A']].copy()
matchA.columns = ['inter_no', 'phase_no', 'move_no']
matchA['ring_type'] = 'A'
matchB = self.match1[['inter_no', 'phas_B', 'move_B']].copy()
matchB.columns = ['inter_no', 'phase_no', 'move_no']
matchB['ring_type'] = 'B'
self.match2 = pd.concat([matchA, matchB]).drop_duplicates()
self.match2 = self.match2[['inter_no', 'phase_no', 'ring_type', 'move_no']]
self.match2 = self.match2.sort_values(by=list(self.match2.columns))
# 2-1-3
def make_match3(self):
'''
각 movement들에 방향(진입방향, 진출방향)을 매칭시켜 추가함.
- match2의 컬럼 : inter_no, phase_no, ring_type, move_no
- match3의 컬럼 : inter_no, phase_no, ring_type, move_no, inc_dir, out_dir
nema :
- 컬럼 : move_no, inc_dir, out_dir
- 모든 종류의 이동류번호에 대하여 진입방향과 진출방향을 매칭시키는 테이블
- 이동류번호 : 1 ~ 16, 17, 18, 21
- 진입, 진출방향(8방위) : 동, 서, 남, 북, 북동, 북서, 남동, 남서
'''
# nema 정보 불러오기 및 병합
self.match3 = pd.merge(self.match2, self.nema, how='left', on='move_no').drop_duplicates()
# 2-1-4
def make_match4(self):
'''
방위각 정보를 매칭시켜 추가함.
- match3의 컬럼 : inter_no, phase_no, ring_type, move_no, inc_dir, out_dir
- match4의 컬럼 : inter_no, phase_no, ring_type, move_no, inc_dir, out_dir, inc_angle, out_angle
angle_original :
- 컬럼 : inter_no, angle_Aj, angle_Bj (j : 1 ~ 8)
- 모든 종류의 이동류번호에 대하여 진입방향과 진출방향을 매칭시키는 테이블
- 이동류번호 : 1 ~ 16, 17, 18, 21
- 진입, 진출방향(8방위) : 동, 서, 남, 북, 북동, 북서, 남동, 남서
'''
# 계층화
angles = []
for i, row in self.angle.iterrows():
angle_codes = row[[f'angle_{alph}{j}' for alph in ['A', 'B'] for j in range(1,9)]]
new = pd.DataFrame({'inter_no':[row.inter_no] * 16, 'phase_no':list(range(1, 9))*2, 'ring_type':['A'] * 8 + ['B'] * 8, 'angle_code':angle_codes.to_list()})
angles.append(new)
angles = pd.concat(angles)
angles = angles.dropna().reset_index(drop=True)
# 병합
six_chars = angles.angle_code.apply(lambda x:len(x)==6)
angles.loc[six_chars,'inc_angle'] = angles.angle_code.apply(lambda x:x[:3])
angles.loc[six_chars,'out_angle'] = angles.angle_code.apply(lambda x:x[3:])
angles = angles.drop('angle_code', axis=1)
self.match4 = pd.merge(self.match3, angles, how='left', left_on=['inter_no', 'phase_no', 'ring_type'],
right_on=['inter_no', 'phase_no', 'ring_type']).drop_duplicates()
# 2-1-5
def make_match5(self):
'''
진입엣지id, 진출엣지id, 노드id를 추가함 (주교차로).
- match4의 컬럼 : inter_no, phase_no, ring_type, move_no, inc_dir, out_dir, inc_angle, out_angle
- match5의 컬럼 : inter_no, phase_no, ring_type, move_no, inc_dir, out_dir, inc_angle, out_angle, inc_edge, out_edge, node_id
사용된 데이터 :
(1) net
- 성남시 정자동 부근의 샘플 네트워크
(2) inter_node
- 교차로번호와 노드id를 매칭시키는 테이블.
- parent/child 정보도 포함되어 있음
- 컬럼 : inter_no, node_id, inter_type
(3) inter_info
- 교차로 정보. 여기에서는 위도와 경도가 쓰임.
- 컬럼 : inter_no, inter_name, inter_lat, inter_lon, group_no, main_phase_no
진입엣지id, 진출엣지id를 얻는 과정 :
- match5 = match4.copy()의 각 열을 순회하면서 아래 과정을 반복함.
* 진입에 대해서만 서술하겠지만 진출도 마찬가지로 설명될 수 있음
- 해당 행의 교차로정보로부터 노드ID를 얻어내고, 해당 노드에 대한 모든 진출엣지id를 inc_edges에 저장.
* inc_edge(진입엣지) : incoming edge, out_edge(진출엣지) : outgoing_edge
- inc_edges의 모든 진입엣지에 대하여 진입방향(inc_dires, 2차원 단위벡터)을 얻어냄.
- 해당 행의 진입각으로부터 그에 대응되는 진입각방향(단위벡터)를 얻어냄.
- 주어진 진입각방향에 대하여 내적이 가장 작은 진입방향에 대한 진입엣지를 inc_edge_id로 지정함.
'''
# parent node만 가져옴.
inter_node1 = self.inter_node[self.inter_node.inter_type == 'parent'].drop('inter_type', axis=1)
inter_info1 = self.inter_info[['inter_no', 'inter_lat', 'inter_lon']]
inter = pd.merge(inter_node1, inter_info1, how='left', left_on=['inter_no'],
right_on=['inter_no']).drop_duplicates()
self.inter2node = dict(zip(inter['inter_no'], inter['node_id']))
self.match5 = self.match4.copy()
# 진입진출ID 매칭
for index, row in self.match5.iterrows():
node_id = self.inter2node[row.inter_no]
node = self.net.getNode(node_id)
# 교차로의 모든 (from / to) edges
inc_edges = [edge for edge in node.getIncoming() if edge.getFunction() == ''] # incoming edges
out_edges = [edge for edge in node.getOutgoing() if edge.getFunction() == ''] # outgoing edges
# 교차로의 모든 (from / to) directions
inc_dirs = []
for inc_edge in inc_edges:
start = inc_edge.getShape()[-2]
end = inc_edge.getShape()[-1]
inc_dir = np.array(end) - np.array(start)
inc_dir = inc_dir / (inc_dir ** 2).sum() ** 0.5
inc_dirs.append(inc_dir)
out_dirs = []
for out_edge in out_edges:
start = out_edge.getShape()[0]
end = out_edge.getShape()[1]
out_dir = np.array(end) - np.array(start)
out_dir = out_dir / (out_dir ** 2).sum() ** 0.5
out_dirs.append(out_dir)
# 진입각, 진출각 불러오기
if not pd.isna(row.inc_angle):
inc_angle = int(row.inc_angle)
out_angle = int(row.out_angle)
# 방위각을 일반각으로 가공, 라디안 변환, 단위벡터로 변환
inc_angle = (-90 - inc_angle) % 360
inc_angle = inc_angle * np.pi / 180.
inc_dir_true = np.array([np.cos(inc_angle), np.sin(inc_angle)])
out_angle = (90 - out_angle) % 360
out_angle = out_angle * np.pi / 180.
out_dir_true = np.array([np.cos(out_angle), np.sin(out_angle)])
# 매칭 엣지 반환
inc_index = np.array([np.dot(inc_dir, inc_dir_true) for inc_dir in inc_dirs]).argmax()
out_index = np.array([np.dot(out_dir, out_dir_true) for out_dir in out_dirs]).argmax()
inc_edge_id = inc_edges[inc_index].getID()
out_edge_id = out_edges[out_index].getID()
self.match5.at[index, 'inc_edge'] = inc_edge_id
self.match5.at[index, 'out_edge'] = out_edge_id
self.match5['node_id'] = self.match5['inter_no'].map(self.inter2node)
self.match5 = self.match5.sort_values(by=['inter_no','phase_no','ring_type']).reset_index(drop=True)
# 2-1-6
def make_match6(self):
'''
진입엣지id, 진출엣지id, 노드id를 추가함 (부교차로).
- match6의 컬럼 : inter_no, phase_no, ring_type, move_no, inc_dir, out_dir, inc_angle, out_angle, inc_edge, out_edge, node_id
사용된 데이터 :
(1) inter_node
- 교차로번호와 노드id를 매칭시키는 테이블.
- parent/child 정보도 포함되어 있음
- 컬럼 : inter_no, node_id, inter_type
(2) uturn (유턴정보)
- 컬럼 : parent_id, child_id, direction, condition, inc_edge, out_edge
- parent_id, child_id : 주교차로id, 유턴교차로id
- direction : 주교차로에 대한 유턴노드의 상대적인 위치(방향)
- condition : 좌회전시, 직진시, 직좌시, 보행신호시 중 하나
- inc_edge, out_edge : 유턴에 대한 진입진출엣지
(3) coord (연동교차로정보)
- 컬럼 : parent_id, child_id, phase_no, ring_type, inc_edge, out_edge
- parent_id, child_id : 주교차로id, 연동교차로id
- 나머지 컬럼 : 각 (현시, 링)별 진입진출엣지
설명 :
- match5는 주교차로에 대해서만 진입엣지id, 진출엣지id, 노드id를 추가했었음.
여기에서 uturn, coord를 사용해서 부교차로들(유턴교차로, 연동교차로)에 대해서도 해당 값들을 부여함.
유턴교차로 :
- directions를 정북기준 시계방향의 8방위로 정함.
- 이를 통해 진입방향이 주어진 경우에 좌회전, 직진, 보행 등에 대한 (진입방향, 진출방향)을 얻어낼 수 있음.
- 예) 진입방향(direction)이 ''일 때,
- 직진 : (북, 남)
* 남 : directions[(ind + 4) % len(directions)]
- 좌회전 : (북, 동)
* 동 : directions[(ind + 2) % len(directions)]
- 보행 : (서, 동)
* 서 : directions[(ind - 2) % len(directions)]
- uturn의 각 행을 순회하면서 아래 과정을 반복함
- match5에서 parent_id에 해당하는 행들을 가져옴(cmatch).
- condition 별로 진입방향, 진출방향A, 진출방향B 정함.
- 상술한 directions를 활용하여 정함.
- (진입방향, 진출방향A, 진출방향B)을 고려하여 (현시, 링) 별로 진입엣지id, 진출엣지id를 정함.
- ex) cmatch.loc[(cmatch.inc_dir==inc_dire) & (cmatch.out_dir==out_dire_A), ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id]
- 순회하면서 만든 cmatch를 cmatchs라는 리스트에 저장함.
연동교차로 :
- 연동교차로의 경우 coord에 (현시, 링)별 진입엣지ID, 진출엣지ID가 명시되어 있음.
- 'inc_dir', 'out_dir', 'inc_angle','out_angle'와 같은 열들은 np.nan을 지정해놓음.
- 이 열들은, 사실상 다음 스텝부터는 사용되지 않는 열들이기 때문에 np.nan으로 지정해놓아도 문제없음.
match6 :
- 이렇게 얻은 match5, cmatchs, coord를 모두 pd.concat하여 match6을 얻어냄.
'''
self.node2inter = dict(zip(self.inter_node['node_id'], self.inter_node['inter_no']))
child_ids = self.inter_node[self.inter_node.inter_type=='child'].node_id.unique()
ch2pa = {} # child to parent
for child_id in child_ids:
parent_no = self.inter_node[self.inter_node.node_id==child_id].inter_no.iloc[0]
sub_inter_node = self.inter_node[self.inter_node.inter_no==parent_no]
ch2pa[child_id] = sub_inter_node[sub_inter_node.inter_type=='parent'].iloc[0].node_id
directions = ['', '북동', '', '남동', '', '남서', '', '북서'] # 정북기준 시계방향으로 8방향
# 각 uturn node에 대하여 (inc_edge_id, out_edge_id) 부여
cmatches = []
for _, row in self.uturn.iterrows():
child_id = row.child_id
parent_id = row.parent_id
direction = row.direction
condition = row.condition
inc_edge_id = row.inc_edge
out_edge_id = row.out_edge
# match5에서 parent_id에 해당하는 행들을 가져옴
cmatch = self.match5.copy()[self.match5.node_id==parent_id] # match dataframe for a child node
cmatch = cmatch.sort_values(by=['phase_no', 'ring_type']).reset_index(drop=True)
cmatch['node_id'] = child_id
cmatch[['inc_edge', 'out_edge']] = np.nan
# condition 별로 inc_dire, out_dire_A, out_dire_B를 정함
ind = directions.index(direction)
if condition == "좌회전시":
inc_dire = direction
out_dire_A = out_dire_B = directions[(ind + 2) % len(directions)]
elif condition == "직진시":
inc_dire = direction
out_dire_A = out_dire_B = directions[(ind + 4) % len(directions)]
elif condition == "보행신호시":
inc_dire = directions[(ind + 2) % len(directions)]
out_dire_A = directions[(ind - 2) % len(directions)]
out_dire_B = directions[(ind - 2) % len(directions)]
# (inc_dire, out_dire_A, out_dire_B) 별로 inc_edge_id, out_edge_id를 정함
if condition == '보행신호시':
cmatch.loc[(cmatch.inc_dir==inc_dire) & (cmatch.out_dir==out_dire_A), ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id]
cmatch.loc[(cmatch.inc_dir==inc_dire) & (cmatch.out_dir==out_dire_B), ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id]
# 이동류번호가 17(보행신호)이면서 유턴노드방향으로 가는 신호가 없으면 (inc_edge_id, out_edge_id)를 부여한다.
cmatch.loc[(cmatch.move_no==17) & (cmatch.out_dir!=direction), ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id]
else: # '직진시', '좌회전시'
cmatch.loc[(cmatch.inc_dir==inc_dire) & (cmatch.out_dir==out_dire_A), ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id]
cmatch.loc[(cmatch.inc_dir==inc_dire) & (cmatch.out_dir==out_dire_B), ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id]
# 유턴신호의 이동류번호를 19로 부여한다.
cmatch.loc[(cmatch.inc_dir==inc_dire) & (cmatch.out_dir==out_dire_A), 'move_no'] = 19
cmatch.loc[(cmatch.inc_dir==inc_dire) & (cmatch.out_dir==out_dire_B), 'move_no'] = 19
cmatches.append(cmatch)
# 각 coordination node에 대하여 (inc_edge_id, out_edge_id) 부여
self.coord['inter_no'] = self.coord['parent_id'].map(self.node2inter)
self.coord = self.coord.rename(columns={'child_id':'node_id'})
self.coord[['inc_dir', 'out_dir', 'inc_angle','out_angle']] = np.nan
self.coord['move_no'] = 20
self.coord = self.coord[['inter_no', 'phase_no', 'ring_type', 'move_no', 'inc_dir', 'out_dir', 'inc_angle','out_angle', 'inc_edge', 'out_edge', 'node_id']]
# display(coord)
cmatches = pd.concat(cmatches)
self.match6 = pd.concat([self.match5, cmatches, self.coord]).drop_duplicates().sort_values(by=['inter_no', 'node_id', 'phase_no', 'ring_type'])
self.match6.to_csv(os.path.join(self.path_root, 'Intermediates', 'match6.csv'))
# 2-1-7
def make_matching(self):
'''
이동류 매칭 : 각 교차로에 대하여, 가능한 모든 이동류 (1~18, 21)에 대한 진입·진출엣지ID를 지정한다.
모든 이동류에 대해 지정하므로, 시차제시 이전과 다른 이동류가 등장하더라도 항상 진입·진출 엣지 ID를 지정할 수 있다.
- matching의 컬럼 : inter_no, move_no, inc_dir, out_dir, inc_edge, out_edge, node_id
설명 :
- 필요한 리스트, 딕셔너리 등을 정의
(1) 가능한 (진입방향, 진출방향) 목록 [리스트]
(2) 각 교차로별 방향 목록 : pdires (possible directions) [딕셔너리]
(3) 각 (교차로, 진입방향) 별 진입id 목록 : inc2id (incoming direction to incoming edge_id) [딕셔너리]
(4) 각 (교차로, 진출방향) 별 진출id 목록 : out2id (outgoing direction to outgoing edge_id) [딕셔너리]
(5) 각 교차로별 가능한 (진입방향, 진출방향) 목록 : pflow (possible flows) [딕셔너리]
- matching은 빈 리스트로 지정.
- 모든 노드id에 대하여 다음 과정을 반복
- 해당 노드id에 대한 모든 가능한 (진입방향, 진출방향)에 대하여 다음 과정을 반복
- (노드id, 진입방향)으로부터 진입엣지id를 얻어냄. 마찬가지로 진출엣지id도 얻어냄
- 얻어낸 정보를 바탕으로 한 행(new_row)을 만들고 이것을 matching에 append
'''
self.match7 = self.match6.copy()
self.match7 = self.match7[['inter_no', 'move_no', 'inc_dir', 'out_dir', 'inc_edge', 'out_edge', 'node_id']]
parent_ids = sorted(self.inter_node[self.inter_node.inter_type=='parent'].node_id.unique())
child_ids = sorted(self.inter_node[self.inter_node.inter_type=='child'].node_id.unique())
# (1) 가능한 (진입방향, 진출방향) 목록
flows = self.nema.dropna().apply(lambda row: (row['inc_dir'], row['out_dir']), axis=1).tolist()
# (2) 각 교차로별 방향 목록 : pdires (possible directions)
pdires = {}
for node_id in parent_ids:
dires = self.match7[self.match7.node_id == node_id][['inc_dir','out_dir']].values.flatten()
dires = {dire for dire in dires if type(dire)==str}
pdires[node_id] = dires
# (3) 각 (교차로, 진입방향) 별 진입id 목록 : inc2id (incoming direction to incoming edge_id)
inc2id = {}
for node_id in parent_ids:
for inc_dir in pdires[node_id]:
df = self.match7[(self.match7.node_id==node_id) & (self.match7.inc_dir==inc_dir)]
inc2id[(node_id, inc_dir)] = df.inc_edge.iloc[0]
# (4) 각 (교차로, 진출방향) 별 진출id 목록 : out2id (outgoing direction to outgoing edge_id)
out2id = {}
for node_id in parent_ids:
for out_dir in pdires[node_id]:
df = self.match7[(self.match7.node_id==node_id) & (self.match7.out_dir==out_dir)]
out2id[(node_id, out_dir)] = df.out_edge.iloc[0]
# (5) 각 교차로별 가능한 (진입방향, 진출방향) 목록 : pflow (possible flows)
pflow = {}
for node_id in parent_ids:
pflow[node_id] = [flow for flow in flows if set(flow).issubset(pdires[node_id])]
# (6) 가능한 이동류에 대하여 진입id, 진출id 배정 : matching
# node2inter = dict(zip(self.match7['node_id'], self.match7['inter_no']))
dires_right = ['', '', '', '', ''] # ex (북, 서), (서, 남) 등은 우회전 flow
self.matching = []
for node_id in parent_ids:
inter_no = self.node2inter[node_id]
# 좌회전과 직진(1 ~ 16)
for (inc_dir, out_dir) in pflow[node_id]:
move_no = self.nema[(self.nema.inc_dir==inc_dir) & (self.nema.out_dir==out_dir)].move_no.iloc[0]
inc_edge = inc2id[(node_id, inc_dir)]
out_edge = out2id[(node_id, out_dir)]
new_row = pd.DataFrame({'inter_no':[inter_no], 'move_no':[move_no],
'inc_dir':[inc_dir], 'out_dir':[out_dir],
'inc_edge':[inc_edge], 'out_edge':[out_edge], 'node_id':[node_id]})
self.matching.append(new_row)
# 보행신호(17), 전적색(18)
new_row = pd.DataFrame({'inter_no':[inter_no] * 2, 'move_no':[17, 18],
'inc_dir':[None]*2, 'out_dir':[None]*2,
'inc_edge':[None]*2, 'out_edge':[None]*2, 'node_id':[node_id]*2})
self.matching.append(new_row)
# 신호우회전(21)
for d in range(len(dires_right)-1):
inc_dir = dires_right[d]
out_dir = dires_right[d+1]
if {inc_dir, out_dir}.issubset(pdires[node_id]):
inc_edge = inc2id[(node_id, inc_dir)]
out_edge = out2id[(node_id, out_dir)]
new_row = pd.DataFrame({'inter_no':[inter_no], 'move_no':[21],
'inc_dir':[inc_dir], 'out_dir':[out_dir],
'inc_edge':[inc_edge], 'out_edge':[out_edge], 'node_id':[node_id]})
self.matching.append(new_row)
self.matching.append(self.match7[self.match7.node_id.isin(child_ids)])
self.matching = pd.concat(self.matching)
self.matching = self.matching.dropna().sort_values(by=['inter_no', 'node_id', 'move_no']).reset_index(drop=True)
self.matching['move_no'] = self.matching['move_no'].astype(int)
self.matching.to_csv(os.path.join(self.path_root, 'Intermediates', 'matching.csv'))
# 2-2
def get_movements(self):
movements_path = os.path.join(self.path_root, 'Intermediates', 'movement')
movements_list = [pd.read_csv(os.path.join(movements_path, file), index_col=0) for file in tqdm(os.listdir(movements_path), desc='이동류정보 불러오는 중 : movements')]
movements = pd.concat(movements_list)
movements = movements.drop(columns=['start_unix'])
movements = movements.drop_duplicates()
movements = movements.sort_values(by=['inter_no', 'phas_A', 'phas_B'])
movements = movements.reset_index(drop=True)
movements.to_csv(os.path.join(self.path_root, 'Intermediates', 'movements.csv'))
return movements
# 2-3 node2num_cycles : A dictionary that maps a node_id to the number of cycles
def get_node2num_cycles(self):
# node2inter = dict(zip(inter_node['node_id'], inter_node['inter_no']))
self.node_ids = sorted(self.inter_node.node_id.unique())
Aplan = self.plan.copy()[['inter_no'] + [f'dura_A{j}' for j in range(1,9)] + ['cycle']]
grouped = Aplan.groupby('inter_no')
df = grouped.agg({'cycle': 'min'}).reset_index()
df = df.rename(columns={'cycle': 'min_cycle'})
df['num_cycle'] = 300 // df['min_cycle'] + 2
inter2num_cycles = dict(zip(df['inter_no'], df['num_cycle']))
node2numcycles = {node_id : inter2num_cycles[self.node2inter[node_id]] for node_id in self.node_ids}
with open(os.path.join('Intermediates','node2numcycles.json'), 'w') as file:
json.dump(node2numcycles, file, indent=4)
return node2numcycles
# 3. 이슈사항 저장
def write_issues(self):
path_issues = os.path.join(self.path_root, "Results", "issues_intermediates.txt")
with open(path_issues, "w", encoding="utf-8") as file:
for item in self.issues:
file.write(item + "\n")
if self.issues:
print("데이터 처리 중 발생한 특이사항은 다음과 같습니다. :")
for review in self.issues:
print(review)
def main(self):
# 1. 데이터 불러오기
self.load_data()
# 2. 중간산출물 만들기
self.get_intermediates()
# 3. 이슈사항 저장
self.write_issues()
if __name__ == '__main__':
self = DailyPreprocessor()
self.main()