|
@ -1,818 +0,0 @@ |
|
|
# python .\Scripts\preprocess_daily.py |
|
|
|
|
|
import pandas as pd |
|
|
|
|
|
import numpy as np |
|
|
|
|
|
import os, sys, copy |
|
|
|
|
|
import json |
|
|
|
|
|
import sumolib, traci |
|
|
|
|
|
from tqdm import tqdm |
|
|
|
|
|
|
|
|
|
|
|
class DailyPreprocessor(): |
|
|
|
|
|
def __init__(self): |
|
|
|
|
|
# 루트폴더 지정 |
|
|
|
|
|
self.path_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
|
|
|
|
|
with open(os.path.join(self.path_root, 'Scripts', 'config.json'), 'r') as config_file: |
|
|
|
|
|
config = json.load(config_file) |
|
|
|
|
|
# 주요 폴더 경로 지정 |
|
|
|
|
|
self.paths = config['paths'] |
|
|
|
|
|
self.path_data = os.path.join(self.path_root, *self.paths['data']) |
|
|
|
|
|
self.path_intermediates = os.path.join(self.path_root, *self.paths['intermediates']) |
|
|
|
|
|
self.path_results = os.path.join(self.path_root, *self.paths['results']) |
|
|
|
|
|
self.path_tables = os.path.join(self.path_root, *self.paths['tables']) |
|
|
|
|
|
self.path_networks = os.path.join(self.path_root, *self.paths['networks']) |
|
|
|
|
|
self.path_scripts = os.path.join(self.path_root, *self.paths['scripts']) |
|
|
|
|
|
|
|
|
|
|
|
# 이슈사항 목록 |
|
|
|
|
|
self.issues = [] |
|
|
|
|
|
|
|
|
|
|
|
# 1. 데이터 불러오기 |
|
|
|
|
|
def load_data(self): |
|
|
|
|
|
print('1. 데이터를 로드합니다.') |
|
|
|
|
|
self.load_networks() |
|
|
|
|
|
self.load_tables() |
|
|
|
|
|
self.check_networks() |
|
|
|
|
|
self.check_tables() |
|
|
|
|
|
|
|
|
|
|
|
# 1-1. 네트워크 불러오기 |
|
|
|
|
|
def load_networks(self): |
|
|
|
|
|
self.net = sumolib.net.readNet(os.path.join(self.path_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.inter_info = pd.read_csv(os.path.join(self.path_tables, 'inter_info.csv'), dtype=loading_dtype) |
|
|
|
|
|
self.angle = pd.read_csv(os.path.join(self.path_tables, 'angle.csv'), dtype=loading_dtype) |
|
|
|
|
|
self.plan = pd.read_csv(os.path.join(self.path_tables, 'plan.csv'), dtype=loading_dtype) |
|
|
|
|
|
self.inter_node = pd.read_csv(os.path.join(self.path_tables, 'inter_node.csv'), dtype=loading_dtype) |
|
|
|
|
|
self.uturn = pd.read_csv(os.path.join(self.path_tables, 'child_uturn.csv'), dtype=loading_dtype) |
|
|
|
|
|
self.coord = pd.read_csv(os.path.join(self.path_tables, 'child_coord.csv'), dtype=loading_dtype) |
|
|
|
|
|
self.nema = pd.read_csv(os.path.join(self.path_tables, 'nema.csv'), encoding='cp949', dtype=loading_dtype) |
|
|
|
|
|
|
|
|
|
|
|
# 교차로목록, 노드목록 정의 |
|
|
|
|
|
self.inter_nos = [int(x) for x in sorted(self.inter_info.inter_no.unique())] |
|
|
|
|
|
self.node_ids = sorted(self.inter_node.node_id.unique()) |
|
|
|
|
|
|
|
|
|
|
|
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_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_plan() |
|
|
|
|
|
self.check_inter_info() |
|
|
|
|
|
self.check_angle() |
|
|
|
|
|
print("1-4. 테이블들의 무결성 검사를 완료했습니다.") |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
# 1-4-1. 신호계획(plan) 검사 |
|
|
|
|
|
def check_plan(self): |
|
|
|
|
|
# 1-4-1-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-1-1. plan의 inter_no 중 교차로 목록(inter_nos)에 포함되지 않는 항목이 있습니다: {missing_inter_nos}" |
|
|
|
|
|
self.issues.append(msg) |
|
|
|
|
|
|
|
|
|
|
|
# 1-4-1-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-1-2. plan에 잘못된 형식의 start_time이 존재합니다: {start_hour, start_minute}" |
|
|
|
|
|
self.issues.append(msg) |
|
|
|
|
|
|
|
|
|
|
|
# 1-4-1-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-1-3. plan에 음수이거나 200보다 큰 현시시간이 존재합니다. : {invalid_inter_nos}" |
|
|
|
|
|
self.issues.append(msg) |
|
|
|
|
|
|
|
|
|
|
|
# 1-4-1-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-1-4. 한 프로그램에 서로 다른 주기가 존재합니다. inter_no:{inc_inter_no}, start_hour:{start_minute}, start_hour:{start_minute}일 때, cycle이 유일하게 결정되지 않습니다." |
|
|
|
|
|
self.issues.append(msg) |
|
|
|
|
|
|
|
|
|
|
|
# 1-4-1-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-1-5. inter_no:{inc_inter_no}, A링현시시간의 합과 B링현시시간의 합이 일치하지 않거나, 현시시간의 합과 주기가 일치하지 않습니다." |
|
|
|
|
|
self.issues.append(msg) |
|
|
|
|
|
|
|
|
|
|
|
# 1-4-2. 교차로정보(inter_info) 검사 |
|
|
|
|
|
def check_inter_info(self): |
|
|
|
|
|
# 1-4-2-1. inter_lat, inter_lon 적절성 검사 |
|
|
|
|
|
# self.inter_info.loc[0, 'inter_lat'] = 38.0 # 에러 발생을 위한 코드 |
|
|
|
|
|
self.max_lon, self.min_lon = 127.3, 127.0 |
|
|
|
|
|
self.max_lat, self.min_lat = 37.5, 37.2 |
|
|
|
|
|
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-2-1. 위도 또는 경도가 범위를 벗어난 교차로가 있습니다: inter_no : {row['inter_no']}" |
|
|
|
|
|
self.issues.append(msg) |
|
|
|
|
|
|
|
|
|
|
|
# 1-4-3. 방위각정보(inter_info) 검사 |
|
|
|
|
|
def check_angle(self): |
|
|
|
|
|
# 1-4-3-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-3-1. angle의 inter_no 중 교차로 목록(inter_nos)에 포함되지 않는 항목이 있습니다: {missing_inter_nos}" |
|
|
|
|
|
self.issues.append(msg) |
|
|
|
|
|
|
|
|
|
|
|
# 1-4-3-2. 각도 코드 검사 |
|
|
|
|
|
angle_codes = self.angle[[f'angle_{alph}{j}' for alph in ['A', 'B'] for j in range(1,9)]].values.flatten() |
|
|
|
|
|
angle_codes = [code for code in angle_codes if not pd.isna(code) and code != 'stop'] |
|
|
|
|
|
of_length_6 = [len(code)==6 for code in angle_codes] |
|
|
|
|
|
if not all(of_length_6): |
|
|
|
|
|
msg = f"1-4-3-2. 여섯자리가 아닌 각도코드가 존재합니다." |
|
|
|
|
|
self.issues.append(msg) |
|
|
|
|
|
angle_codes = [[code[:3],code[3:]] for code in angle_codes] |
|
|
|
|
|
angle_codes = [int(item) for sublist in angle_codes for item in sublist] |
|
|
|
|
|
angle_codes = [0<=code<360 for code in angle_codes] |
|
|
|
|
|
if not all(angle_codes): |
|
|
|
|
|
msg = f"1-4-3-2. 0과 359 사이의 값을 벗어나는 방위각이 존재합니다." |
|
|
|
|
|
self.issues.append(msg) |
|
|
|
|
|
|
|
|
|
|
|
# 2. 중간산출물 만들기 |
|
|
|
|
|
def get_intermediates(self): |
|
|
|
|
|
print('2. 중간산출물을 생성합니다.') |
|
|
|
|
|
self.get_matches() |
|
|
|
|
|
self.initialize_state() |
|
|
|
|
|
self.assign_signals() |
|
|
|
|
|
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() |
|
|
|
|
|
print('2-1. 매칭 테이블들을 생성했습니다.') |
|
|
|
|
|
|
|
|
|
|
|
# 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_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='이동류정보 불러오는 중')] |
|
|
|
|
|
df = pd.concat(moves).reset_index(drop=True) |
|
|
|
|
|
self.match1 = [] |
|
|
|
|
|
for i, group in df.groupby(['inter_no', 'phas_A', 'phas_B']): |
|
|
|
|
|
inter_no, phas_A, phas_B = i |
|
|
|
|
|
pairs_array = np.array(group[['move_A', 'move_B']]) |
|
|
|
|
|
unique_pairs, counts = np.unique(pairs_array, axis=0, return_counts=True) |
|
|
|
|
|
frequent_pair = unique_pairs[np.argmax(counts)] |
|
|
|
|
|
self.match1.append(pd.DataFrame({'inter_no':[inter_no], 'phas_A':[phas_A], 'phas_B':[phas_B], |
|
|
|
|
|
'move_A':[frequent_pair[0]], 'move_B':[frequent_pair[1]]})) |
|
|
|
|
|
self.match1 = pd.concat(self.match1).reset_index(drop=True) |
|
|
|
|
|
self.match1.to_csv(os.path.join(self.path_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'])) |
|
|
|
|
|
|
|
|
|
|
|
# node_id, inc/out_edge가 주어질 때 해당되는 방향벡터를 매칭하는 딕셔너리 |
|
|
|
|
|
self.node_id2inc_edge2dir = dict() |
|
|
|
|
|
self.node_id2out_edge2dir = dict() |
|
|
|
|
|
|
|
|
|
|
|
# 진입진출ID 매칭 |
|
|
|
|
|
self.match5 = self.match4.copy() |
|
|
|
|
|
for index, row in self.match5.iterrows(): |
|
|
|
|
|
node_id = self.inter2node[row.inter_no] |
|
|
|
|
|
node = self.net.getNode(node_id) |
|
|
|
|
|
self.node_id2inc_edge2dir[node_id] = dict() |
|
|
|
|
|
self.node_id2out_edge2dir[node_id] = dict() |
|
|
|
|
|
# 교차로의 모든 (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) |
|
|
|
|
|
self.node_id2inc_edge2dir[node_id][inc_edge.getID()] = inc_dir |
|
|
|
|
|
out_dirs = [] |
|
|
|
|
|
self.out_edge2dir = dict() |
|
|
|
|
|
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) |
|
|
|
|
|
self.out_edge2dir[out_edge] = out_dir |
|
|
|
|
|
self.node_id2out_edge2dir[node_id][out_edge.getID()] = 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) |
|
|
|
|
|
|
|
|
|
|
|
# 정북기준 시계방향으로 8방향 |
|
|
|
|
|
self.directions = ['북', '북동', '동', '남동', '남', '남서', '서', '북서'] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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 별로 진입방향, 진출방향을 정함. |
|
|
|
|
|
- 상술한 directions를 활용하여 정함. |
|
|
|
|
|
- (진입방향, 진출방향)을 고려하여 (현시, 링) 별로 진입엣지id, 진출엣지id를 정함. |
|
|
|
|
|
- ex) cmatch.loc[(cmatch.inc_dir==inc_dire) & (cmatch.out_dir==out_dire), ['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'])) |
|
|
|
|
|
|
|
|
|
|
|
self.parent_ids = sorted(self.inter_node[self.inter_node.inter_type=='parent'].node_id.unique()) |
|
|
|
|
|
self.child_ids = sorted(self.inter_node[self.inter_node.inter_type=='child'].node_id.unique()) |
|
|
|
|
|
self.uturn_ids = sorted(self.uturn.child_id.unique()) |
|
|
|
|
|
self.coord_ids = sorted(self.coord.child_id.unique()) |
|
|
|
|
|
|
|
|
|
|
|
# ids |
|
|
|
|
|
ids = {'node_ids' : self.node_ids, |
|
|
|
|
|
'parent_ids': self.parent_ids, |
|
|
|
|
|
'child_ids' : self.child_ids, |
|
|
|
|
|
'uturn_ids' : self.uturn_ids, |
|
|
|
|
|
'coord_ids' : self.coord_ids, |
|
|
|
|
|
'inter_nos' : self.inter_nos} |
|
|
|
|
|
with open(os.path.join(self.path_intermediates, 'ids.json'), 'w') as file: |
|
|
|
|
|
json.dump(ids, file) |
|
|
|
|
|
|
|
|
|
|
|
ch2pa = {} # child to parent |
|
|
|
|
|
for child_id in self.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 |
|
|
|
|
|
|
|
|
|
|
|
# 각 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에서 부모노드id에 해당하는 행들을 가져옴 (cmatch) |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
# 보행신호시/좌회전시 진입/진출방향 |
|
|
|
|
|
ind = self.directions.index(direction) |
|
|
|
|
|
inc_dire_pedes = self.directions[(ind + 2) % len(self.directions)] |
|
|
|
|
|
out_dire_pedes = self.directions[(ind - 2) % len(self.directions)] |
|
|
|
|
|
inc_dire_right = direction |
|
|
|
|
|
out_dire_right = self.directions[(ind + 2) % len(self.directions)] |
|
|
|
|
|
|
|
|
|
|
|
# 보행신호시/좌회전시 조건 |
|
|
|
|
|
pedes_exists = (cmatch.inc_dir==inc_dire_pedes) & (cmatch.out_dir==out_dire_pedes) |
|
|
|
|
|
right_exists = (cmatch.inc_dir==inc_dire_right) & (cmatch.out_dir==out_dire_right) |
|
|
|
|
|
|
|
|
|
|
|
# 보행신호시/좌회전시 진입/진출 엣지id 배정 |
|
|
|
|
|
ind = self.directions.index(direction) |
|
|
|
|
|
if condition == "보행신호시": |
|
|
|
|
|
cmatch.loc[pedes_exists, ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id] |
|
|
|
|
|
elif condition == "좌회전시": |
|
|
|
|
|
cmatch.loc[right_exists, ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id] |
|
|
|
|
|
|
|
|
|
|
|
# 신호없음이동류발생시/보행신호이동류발생시 조건 |
|
|
|
|
|
all_redsigns = cmatch.move_no == 18 |
|
|
|
|
|
crosswalk_on = cmatch.move_no == 17 |
|
|
|
|
|
|
|
|
|
|
|
# 만약 어떤 유턴신호도 배정되지 않았다면 |
|
|
|
|
|
# 좌회전시 → 보행신호시 → 보행신호이동류발생시 → 신호없음이동류발생시 순으로 진입/진출 엣지id 배정 |
|
|
|
|
|
uturn_not_assigned = cmatch[['inc_edge','out_edge']].isna().any(axis=1).all() |
|
|
|
|
|
if uturn_not_assigned: |
|
|
|
|
|
# 좌회전시 |
|
|
|
|
|
if right_exists.any(): |
|
|
|
|
|
cmatch.loc[right_exists, ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id] |
|
|
|
|
|
# 보행신호시 |
|
|
|
|
|
elif pedes_exists.any(): |
|
|
|
|
|
cmatch.loc[pedes_exists, ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id] |
|
|
|
|
|
# 보행신호이동류(17) 발생시 |
|
|
|
|
|
elif crosswalk_on.any(): |
|
|
|
|
|
cmatch.loc[crosswalk_on & (cmatch.out_dir!=direction), ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id] |
|
|
|
|
|
# 신호없음이동류(18) 발생시 |
|
|
|
|
|
elif all_redsigns.any(): |
|
|
|
|
|
cmatch.loc[all_redsigns & (cmatch.out_dir!=direction), ['inc_edge', 'out_edge']] = [inc_edge_id, out_edge_id] |
|
|
|
|
|
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_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 self.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 self.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 self.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 self.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 self.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(self.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_intermediates, 'matching.csv')) |
|
|
|
|
|
|
|
|
|
|
|
# 2-2 신호 초기화 |
|
|
|
|
|
def initialize_state(self): |
|
|
|
|
|
''' |
|
|
|
|
|
비보호우회전신호 (g) 배정 |
|
|
|
|
|
|
|
|
|
|
|
input : |
|
|
|
|
|
(1) net : 네트워크 |
|
|
|
|
|
(2) nodes : 노드 목록 |
|
|
|
|
|
(3) histids : 모든 교차로에 대한 시작유닉스 (시작유닉스, A현시, B현시)별 현시시간, 진입·진출엣지 |
|
|
|
|
|
|
|
|
|
|
|
output : node2init |
|
|
|
|
|
- 각 노드를 초기화된 신호로 맵핑하는 딕셔너리 |
|
|
|
|
|
- 초기화된 신호란, 우회전을 g로 나머지는 r로 지정한 신호를 말함. |
|
|
|
|
|
''' |
|
|
|
|
|
self.nodes = [self.net.getNode(node_id) for node_id in self.node_ids] |
|
|
|
|
|
self.node2init = {} |
|
|
|
|
|
# 모든 노드들을 순회 |
|
|
|
|
|
for node in self.nodes: |
|
|
|
|
|
node_id = node.getID() |
|
|
|
|
|
# 모든 connection |
|
|
|
|
|
conns = [(c.getJunctionIndex(), c) for c in node.getConnections()] |
|
|
|
|
|
conns = [c for c in conns if c[0] >= 0] |
|
|
|
|
|
conns = sorted(conns, key=lambda x: x[0]) |
|
|
|
|
|
state = [] |
|
|
|
|
|
# i번째 connection : ci |
|
|
|
|
|
for i, ci in conns: |
|
|
|
|
|
if ci.getTLLinkIndex() < 0: |
|
|
|
|
|
continue |
|
|
|
|
|
are_foes = False |
|
|
|
|
|
# j번째 connection : cj |
|
|
|
|
|
# 합류지점이 다르면서 상충되는 cj가 존재하면 are_foes = True (r) |
|
|
|
|
|
# 그외의 경우에는 are_foes = False (g) |
|
|
|
|
|
for j, cj in conns: |
|
|
|
|
|
# ci, cj의 합류지점이 같으면 통과 |
|
|
|
|
|
if ci.getTo() == cj.getTo(): |
|
|
|
|
|
continue |
|
|
|
|
|
# ci, cj가 상충되면 are_foes를 True로 지정. |
|
|
|
|
|
if node.areFoes(i, j): |
|
|
|
|
|
are_foes = True |
|
|
|
|
|
break |
|
|
|
|
|
state.append('r' if are_foes else 'g') |
|
|
|
|
|
self.node2init[node_id] = state |
|
|
|
|
|
|
|
|
|
|
|
# 어떤 연결과도 상충이 일어나지는 않지만, 신호가 부여되어 있는 경우에는 r을 부여 |
|
|
|
|
|
for _, row in self.matching.iterrows(): |
|
|
|
|
|
node_id = row.node_id |
|
|
|
|
|
move_no = row.move_no |
|
|
|
|
|
inc_edge = row.inc_edge |
|
|
|
|
|
out_edge = row.out_edge |
|
|
|
|
|
if move_no != 21: |
|
|
|
|
|
inc_edge = self.net.getEdge(inc_edge) |
|
|
|
|
|
out_edge = self.net.getEdge(out_edge) |
|
|
|
|
|
for conn in inc_edge.getConnections(out_edge): |
|
|
|
|
|
index = conn.getTLLinkIndex() |
|
|
|
|
|
if index >= 0: |
|
|
|
|
|
self.node2init[node_id][index] = 'r' |
|
|
|
|
|
|
|
|
|
|
|
# 연등교차로 |
|
|
|
|
|
for _, row in self.coord.iterrows(): |
|
|
|
|
|
node_id = row.node_id |
|
|
|
|
|
inc_edge = row.inc_edge |
|
|
|
|
|
out_edge = row.out_edge |
|
|
|
|
|
if not (pd.isna(inc_edge) and pd.isna(out_edge)): |
|
|
|
|
|
inc_edge = self.net.getEdge(inc_edge) |
|
|
|
|
|
out_edge = self.net.getEdge(out_edge) |
|
|
|
|
|
for conn in inc_edge.getConnections(out_edge): |
|
|
|
|
|
index = conn.getTLLinkIndex() |
|
|
|
|
|
if index >= 0: |
|
|
|
|
|
self.node2init[node_id][index] = 'r' |
|
|
|
|
|
# 유턴교차로 |
|
|
|
|
|
for _, row in self.uturn.iterrows(): |
|
|
|
|
|
node_id = row.child_id |
|
|
|
|
|
inc_edge = row.inc_edge |
|
|
|
|
|
out_edge = row.out_edge |
|
|
|
|
|
if not (pd.isna(inc_edge) and pd.isna(out_edge)): |
|
|
|
|
|
inc_edge = self.net.getEdge(inc_edge) |
|
|
|
|
|
out_edge = self.net.getEdge(out_edge) |
|
|
|
|
|
for conn in inc_edge.getConnections(out_edge): |
|
|
|
|
|
index = conn.getTLLinkIndex() |
|
|
|
|
|
if index >= 0: |
|
|
|
|
|
self.node2init[node_id][index] = 'r' |
|
|
|
|
|
|
|
|
|
|
|
# json 파일로 저장 |
|
|
|
|
|
with open(os.path.join(self.path_intermediates, 'node2init.json'), 'w') as file: |
|
|
|
|
|
json.dump(self.node2init, file) |
|
|
|
|
|
|
|
|
|
|
|
print('2-2. 비보호우회전(g)을 배정했습니다.') |
|
|
|
|
|
|
|
|
|
|
|
# 2-3 신호배정 |
|
|
|
|
|
def assign_signals(self): |
|
|
|
|
|
# assign signals on matching |
|
|
|
|
|
self.matching['init_state'] = self.matching['node_id'].map(self.node2init) |
|
|
|
|
|
self.matching['state'] = self.matching['init_state'].map(lambda x:''.join(x)) |
|
|
|
|
|
# matching의 각 행을 순회 |
|
|
|
|
|
for row in self.matching.itertuples(index=True): |
|
|
|
|
|
node_id = row.node_id |
|
|
|
|
|
move_no = row.move_no |
|
|
|
|
|
inc_edge = row.inc_edge |
|
|
|
|
|
out_edge = row.out_edge |
|
|
|
|
|
state = copy.deepcopy(self.node2init)[node_id] |
|
|
|
|
|
|
|
|
|
|
|
if move_no != 21: |
|
|
|
|
|
inc_edge = self.net.getEdge(inc_edge) |
|
|
|
|
|
out_edge = self.net.getEdge(out_edge) |
|
|
|
|
|
for conn in inc_edge.getConnections(out_edge): |
|
|
|
|
|
index = conn.getTLLinkIndex() |
|
|
|
|
|
if index >= 0: |
|
|
|
|
|
state[index] = 'G' |
|
|
|
|
|
self.matching.at[row.Index, 'state'] = ''.join(state) |
|
|
|
|
|
|
|
|
|
|
|
self.matching = self.matching.dropna(subset='state') |
|
|
|
|
|
self.matching = self.matching.reset_index(drop=True) |
|
|
|
|
|
self.matching = self.matching[['inter_no', 'node_id', 'move_no', 'state']] |
|
|
|
|
|
|
|
|
|
|
|
# assign signals on match6 |
|
|
|
|
|
|
|
|
|
|
|
self.match6 = self.match6.reset_index(drop=True) |
|
|
|
|
|
self.match6['init_state'] = self.match6['node_id'].map(self.node2init) |
|
|
|
|
|
self.match6['state'] = self.match6['init_state'].map(lambda x:''.join(x)) |
|
|
|
|
|
|
|
|
|
|
|
# match6의 각 행을 순회 |
|
|
|
|
|
for i, row in self.match6.iterrows(): |
|
|
|
|
|
node_id = row.node_id |
|
|
|
|
|
move_no = row.move_no |
|
|
|
|
|
inc_edge = row.inc_edge |
|
|
|
|
|
out_edge = row.out_edge |
|
|
|
|
|
state = copy.deepcopy(self.node2init)[node_id] |
|
|
|
|
|
if (pd.isna(inc_edge)) or (pd.isna(out_edge)): |
|
|
|
|
|
continue |
|
|
|
|
|
if (move_no != 21): |
|
|
|
|
|
# print(i, node_id, move_no, ''.join(state)) |
|
|
|
|
|
inc_edge = self.net.getEdge(inc_edge) |
|
|
|
|
|
out_edge = self.net.getEdge(out_edge) |
|
|
|
|
|
for conn in inc_edge.getConnections(out_edge): |
|
|
|
|
|
index = conn.getTLLinkIndex() |
|
|
|
|
|
if index >= 0: |
|
|
|
|
|
state[index] = 'G' |
|
|
|
|
|
# print(i, node_id, move_no, index, ''.join(state)) |
|
|
|
|
|
self.match6.at[i, 'state'] = ''.join(state) |
|
|
|
|
|
|
|
|
|
|
|
self.match6 = self.match6.dropna(subset='state') |
|
|
|
|
|
self.match6 = self.match6.reset_index(drop=True) |
|
|
|
|
|
self.match6 = self.match6[['inter_no', 'node_id', 'phase_no', 'ring_type', 'move_no', 'state']] |
|
|
|
|
|
self.match6.to_csv(os.path.join(self.path_intermediates, 'match6.csv')) |
|
|
|
|
|
self.matching.to_csv(os.path.join(self.path_intermediates, 'matching.csv')) |
|
|
|
|
|
print('2-3. 직진 및 좌회전(G)을 배정했습니다.') |
|
|
|
|
|
|
|
|
|
|
|
uid2uindex = {} |
|
|
|
|
|
for uid in self.uturn_ids: |
|
|
|
|
|
states = self.match6[self.match6.node_id==uid].state.unique() |
|
|
|
|
|
for state in states: |
|
|
|
|
|
if 'G' in state: |
|
|
|
|
|
index = state.index('G') |
|
|
|
|
|
uid2uindex[uid] = index |
|
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
# json 파일로 저장 |
|
|
|
|
|
with open(os.path.join(self.path_intermediates, 'uid2uindex.json'), 'w') as file: |
|
|
|
|
|
json.dump(uid2uindex, file) |
|
|
|
|
|
|
|
|
|
|
|
# 2-4 node2num_cycles : A dictionary that maps a node_id to the number of cycles |
|
|
|
|
|
def get_node2num_cycles(self): |
|
|
|
|
|
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'])) |
|
|
|
|
|
node2num_cycles = {node_id : inter2num_cycles[self.node2inter[node_id]] for node_id in self.node_ids} |
|
|
|
|
|
with open(os.path.join(self.path_intermediates,'node2num_cycles.json'), 'w') as file: |
|
|
|
|
|
json.dump(node2num_cycles, file, indent=4) |
|
|
|
|
|
print("2-2. node2num_cycles.json를 저장했습니다.") |
|
|
|
|
|
|
|
|
|
|
|
# 3. 이슈사항 저장 |
|
|
|
|
|
def write_issues(self): |
|
|
|
|
|
print('3. 이슈사항을 저장합니다.') |
|
|
|
|
|
path_issues = os.path.join(self.path_results, "issues_preprocess_daily.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() |
|
|
|