@ -1,3 +1,4 @@ | |||
Data/tables/move/* | |||
Intermediates/histid/* | |||
Intermediates/movement/* | |||
Intermediates/movement/* | |||
Analysis/0215_augmentation/* |
@ -0,0 +1,61 @@ | |||
inter_no,phase_no,ring_type,flow_lat_1,flow_lon_1,flow_lat_2,flow_lon_2,flow_lat_3,flow_lon_3 | |||
175,1,A,37.36825,127.11491,37.36857,127.1149,37.36869,127.11491 | |||
175,1,B,37.3689,127.11467,37.36857,127.11465,37.36845,127.11466 | |||
175,2,A,37.36889,127.11491,37.36857,127.1149,37.36856,127.11505 | |||
175,2,B,37.36824,127.11466,37.36857,127.11465,37.36857,127.11451 | |||
175,3,A,37.36867,127.11519,37.36867,127.11478,37.36867,127.11463 | |||
175,3,B,37.36847,127.11518,37.36847,127.11478,37.36835,127.11478 | |||
175,4,A,37.36866,127.11437,37.36867,127.11478,37.36878,127.11478 | |||
175,4,B,37.36847,127.11437,37.36847,127.11478,37.36847,127.11493 | |||
176,1,A,37.36691,127.11493,37.36724,127.11493,37.36735,127.11493 | |||
176,1,B,37.36756,127.11467,37.36723,127.11468,37.36712,127.11468 | |||
176,2,A,37.36691,127.11493,37.36724,127.11493,37.36735,127.11493 | |||
176,2,B,37.36691,127.11468,37.36724,127.11468,37.36724,127.11453 | |||
176,3,A,37.36734,127.1144,37.36734,127.11481,37.36745,127.1148 | |||
177,1,A,37.36587,127.11492,37.36619,127.11492,37.36631,127.11492 | |||
177,1,B,37.36652,127.11468,37.36619,127.11467,37.36608,127.11468 | |||
177,2,A,37.36652,127.11492,37.36619,127.11492,37.36619,127.11507 | |||
177,2,B,37.36587,127.11468,37.36619,127.11467,37.36619,127.11453 | |||
177,3,A,19.69448,117.9926,19.69448,117.9926,19.69448,117.9926 | |||
177,4,A,37.36628,127.11439,37.36629,127.1148,37.36641,127.1148 | |||
177,4,B,37.36609,127.1152,37.36609,127.1148,37.36598,127.1148 | |||
178,1,A,37.36313,127.11493,37.36346,127.11493,37.36358,127.11493 | |||
178,1,B,37.36378,127.11468,37.36346,127.11468,37.36334,127.11468 | |||
178,2,A,37.36378,127.11493,37.36346,127.11493,37.36346,127.11507 | |||
178,2,B,37.36313,127.11468,37.36346,127.11468,37.36346,127.11453 | |||
178,3,A,37.36356,127.1144,37.36356,127.1148,37.36367,127.1148 | |||
178,3,B,37.36336,127.11439,37.36336,127.1148,37.36336,127.11495 | |||
178,4,A,37.36356,127.11521,37.36356,127.1148,37.36356,127.11465 | |||
178,4,B,37.36336,127.11521,37.36336,127.1148,37.36324,127.1148 | |||
201,1,A,37.36822,127.10996,37.36854,127.10996,37.36866,127.10996 | |||
201,1,B,37.36822,127.10971,37.36854,127.10971,37.36854,127.10957 | |||
201,2,A,37.36864,127.10943,37.36864,127.10984,37.36876,127.10984 | |||
201,2,B,37.36844,127.10943,37.36844,127.10984,37.36844,127.10999 | |||
201,3,A,37.36864,127.11025,37.36864,127.10984,37.36864,127.10969 | |||
201,3,B,37.36844,127.10943,37.36844,127.10984,37.36844,127.10999 | |||
201,4,A,37.36864,127.11025,37.36864,127.10984,37.36864,127.10969 | |||
201,4,B,37.36844,127.11025,37.36844,127.10984,37.36832,127.10984 | |||
201,5,A,37.36887,127.10996,37.36854,127.10996,37.36854,127.11011 | |||
201,5,B,37.36886,127.10971,37.36854,127.10971,37.36842,127.10971 | |||
202,1,A,37.36865,127.11282,37.36865,127.11241,37.36865,127.11227 | |||
202,1,B,37.36845,127.11201,37.36845,127.11241,37.36845,127.11256 | |||
202,2,A,19.69448,117.9926,19.69448,117.9926,19.69448,117.9926 | |||
206,1,A,37.36451,127.10994,37.36483,127.10994,37.36495,127.10994 | |||
206,1,B,37.36516,127.10969,37.36484,127.10969,37.36472,127.10969 | |||
206,2,A,19.69448,117.9926,19.69448,117.9926,19.69448,117.9926 | |||
206,3,A,37.36451,127.10994,37.36483,127.10994,37.36495,127.10994 | |||
206,3,B,37.36516,127.10969,37.36484,127.10969,37.36472,127.10969 | |||
206,4,A,19.69448,117.9926,19.69448,117.9926,19.69448,117.9926 | |||
210,1,A,37.36357,127.11023,37.36357,127.10982,37.36357,127.10968 | |||
210,2,A,37.36356,127.10941,37.36357,127.10982,37.36369,127.10982 | |||
210,2,B,37.36337,127.10942,37.36337,127.10982,37.36337,127.10997 | |||
210,3,A,37.3638,127.10994,37.36347,127.10995,37.36347,127.11009 | |||
210,3,B,37.3638,127.1097,37.36347,127.1097,37.36336,127.1097 | |||
210,4,A,37.36315,127.10995,37.36347,127.10995,37.36359,127.10995 | |||
210,4,B,37.36315,127.1097,37.36347,127.1097,37.36347,127.10955 | |||
211,1,A,37.36356,127.11277,37.36356,127.11237,37.36356,127.11222 | |||
211,1,B,37.36336,127.11196,37.36336,127.11237,37.36336,127.11252 | |||
211,2,A,19.69448,117.9926,19.69448,117.9926,19.69448,117.9926 | |||
212,1,A,37.36357,127.11347,37.36357,127.11306,37.36357,127.11291 | |||
212,1,B,37.36336,127.11266,37.36337,127.11306,37.36337,127.11321 | |||
212,2,A,19.69448,117.9926,19.69448,117.9926,19.69448,117.9926 |
@ -0,0 +1,274 @@ | |||
import os | |||
import sys | |||
if '__file__' in globals(): | |||
script_dir = os.path.dirname(os.path.abspath(__file__)) | |||
sys.path.append(script_dir) | |||
import numpy as np | |||
import pandas as pd | |||
GREEN_SET = {'g', 'G'} | |||
YELLOW_SET = {'y'} | |||
RED_SET = {'r'} | |||
def get_intermediate_signal_state(prev, cur, signal_type='yellow'): | |||
''' | |||
https://github.com/cts198859/deeprl_signal_control/blob/master/envs/env.py#L128-L152 | |||
'signal_type' has one of the following two values. ['yellow', 'red'] | |||
''' | |||
# 이전신호와 같을때 | |||
if prev == cur: | |||
return cur, False | |||
# 각 인덱스에 해당하는 문자를 비교 | |||
switch_reds, switch_greens = [], [] | |||
for i, (p0, p1) in enumerate(zip(prev, cur)): | |||
# 녹색에서 적색으로 바뀔 때 | |||
if (p0 in GREEN_SET) and (p1 == 'r'): | |||
switch_reds.append(i) | |||
# 적색에서 녹색으로 바뀔 때 | |||
elif (p0 in 'r') and (p1 in GREEN_SET): | |||
switch_greens.append(i) | |||
# 녹색에서 적색으로 바뀌는 경우가 없으면 | |||
if (not switch_reds) and (signal_type == 'yellow'): | |||
return cur, False | |||
mid = list(cur) | |||
for i in switch_reds: | |||
if signal_type == 'yellow': | |||
mid[i] = 'y' | |||
for i in switch_greens: | |||
mid[i] = 'r' | |||
return ''.join(mid), True | |||
class SetupTLSProgram: | |||
def __init__(self, durations, reds, yellows, offset, states, path_tll_xml, name=None): | |||
self.durations = self.as_array(durations) | |||
self.reds = self.as_array(reds) | |||
self.yellows = self.as_array(yellows) | |||
self.offset = offset | |||
self.states = states | |||
self.path_tll_xml = path_tll_xml | |||
self.name = name | |||
self.num_phase = len(states) // 2 | |||
def as_array(self, x): | |||
if isinstance(x, list): | |||
return np.array(x) | |||
return x | |||
def _val(self): | |||
# 길이가 짝수인지 확인 | |||
if len(self.states) % 2 != 0: | |||
raise ValueError("It must have an even number of elements. Odd length detected.") | |||
length = self.num_phase * 2 | |||
if length != len(self.durations) or length != len(self.reds) or length != len(self.yellows): | |||
raise ValueError("The length must all match length.") | |||
if len(set([len(s) for s in self.states])) != 1: | |||
ValueError("All elements in 'states' must have the same length.") | |||
if np.sum(self.durations[:self.num_phase]) != np.sum(self.durations[self.num_phase:]): | |||
raise ValueError("The sums of the two halves of durations do not match") | |||
self._check_always_red(self.states) | |||
self._validate_non_negative_elements(self.durations) | |||
self._validate_non_negative_elements(self.reds) | |||
self._validate_non_negative_elements(self.yellows) | |||
self._validate_non_negative_elements(self.durations - self.reds - self.yellows) | |||
def _validate_non_negative_elements(self, lst): | |||
if any(x < 0 for x in lst): | |||
raise ValueError('The list contains values less than 0. Please ensure all elements in the list are non-negative.') | |||
def _check_always_red(self, states): | |||
colors = ['r'] * len(states[0]) | |||
for s in states: | |||
for i, char in enumerate(s.lower()): | |||
if char != 'r': | |||
colors[i] = char | |||
if 'r' in colors: | |||
raise ValueError("'r' is not allowed in the colors collection.") | |||
def _merge(self, s1, s2): | |||
if len(s1) != len(s2): | |||
raise ValueError("The lengths of s1 and s2 must be the same.") | |||
new_s = [] | |||
for c1, c2 in zip(s1, s2): | |||
if c1 == 'r': | |||
new_s.append(c2) | |||
elif c2 == 'r': | |||
new_s.append(c1) | |||
elif c1 == c2: | |||
new_s.append(c1) | |||
else: | |||
raise ValueError(f"Unexpected characters encountered: c1={c1}, c2={c2}") | |||
return ''.join(new_s) | |||
def _add(self, durations, reds, yellows, states, ring_type='None'): | |||
greens = durations - reds - yellows | |||
colors = [] | |||
phase_numbers = [] | |||
new_states = [] | |||
for curr_i in range(len(states)): | |||
prev_i = (curr_i - 1 + len(states)) % len(states) | |||
next_i = (curr_i + 1) % len(states) | |||
phase_no = curr_i + 1 | |||
r_state, _ = get_intermediate_signal_state(states[prev_i], states[curr_i], signal_type='red') | |||
y_state, has_y = get_intermediate_signal_state(states[curr_i], states[next_i], signal_type='yellow') | |||
g_state = states[curr_i] | |||
# red | |||
if reds[curr_i] != 0: | |||
new_states += [r_state] * reds[curr_i] | |||
colors += [1] * reds[curr_i] # 'r' | |||
phase_numbers += [phase_no] * reds[curr_i] | |||
# green | |||
new_states += [g_state] * greens[curr_i] | |||
colors += [2] * greens[curr_i] # 'g' | |||
phase_numbers += [phase_no] * greens[curr_i] | |||
# yellow | |||
if has_y and yellows[curr_i] == 0: | |||
raise ValueError('Yellow signal is required, but the yellow duration is 0.') | |||
if not has_y and yellows[curr_i] != 0: | |||
y_state = g_state | |||
new_states += [y_state] * yellows[curr_i] | |||
colors += [3] * yellows[curr_i] # 'y' | |||
phase_numbers += [phase_no] * yellows[curr_i] | |||
df = pd.DataFrame( | |||
{ | |||
f'{ring_type}_ring_phase_no':phase_numbers, | |||
f'{ring_type}_ring_color':colors, | |||
f'{ring_type}_ring_state':new_states} | |||
) | |||
return df | |||
def write_tll_xml(self, df_plan, path_tll_xml): | |||
strings = ['<tlLogics>\n'] | |||
tllogic_id = 'None' if self.name is None else self.name | |||
strings.append(f' <tlLogic id="{tllogic_id}" type="static" programID="tllogic_id" offset="{self.offset}">\n') | |||
for _, row in df_plan.iterrows(): | |||
name = str(row['A_ring_phase_no']) + row['A_ring_color'] | |||
name += '_' + str(row['B_ring_phase_no']) + row['B_ring_color'] | |||
duration = row['duration'] | |||
signal_state = row['signal_state'] | |||
strings.append(f' <phase duration="{duration}" name="{name}" state="{signal_state}"/>\n') | |||
strings.append(' </tlLogic>\n') | |||
strings.append('</tlLogics>') | |||
strings = ''.join(strings) | |||
with open(path_tll_xml, 'w') as f: | |||
f.write(strings) | |||
def main(self): | |||
durations, reds, yellows, states = self.durations, self.reds, self.yellows, self.states | |||
n = self.num_phase | |||
self._val() | |||
df_a = self._add(durations[:n], reds[:n], yellows[:n], states[:n], 'A') | |||
df_b = self._add(durations[n:], reds[n:], yellows[n:], states[n:], 'B') | |||
group_cols = ['A_ring_phase_no', 'A_ring_color', 'A_ring_state', 'B_ring_phase_no', 'B_ring_color', 'B_ring_state'] | |||
sort_cols = ['A_ring_phase_no', 'A_ring_color', 'B_ring_phase_no', 'B_ring_color'] | |||
df_plan = pd.concat([df_a, df_b], axis=1) \ | |||
.groupby(group_cols) \ | |||
.size() \ | |||
.reset_index(name='duration') \ | |||
.sort_values(by=sort_cols) \ | |||
.reset_index(drop=True) | |||
mapping = {1: 'r', 2: 'g', 3: 'y'} | |||
df_plan['A_ring_color'] = df_plan['A_ring_color'].map(mapping) | |||
df_plan['B_ring_color'] = df_plan['B_ring_color'].map(mapping) | |||
df_plan['signal_state'] = df_plan.apply(lambda row: self._merge(row['A_ring_state'], row['B_ring_state']), axis=1) | |||
assert df_plan['duration'].sum() == np.sum(durations[:n]) | |||
assert df_plan['duration'].sum() == np.sum(durations[n:]) | |||
self.write_tll_xml(df_plan, self.path_tll_xml) | |||
return df_plan | |||
if __name__ == '__main__': | |||
''' | |||
Test data | |||
- 교차로번호: 5034 | |||
- 앞에서 부터 순서대로 A링1현시, A링2현시, ..., B링1현시, B링2현시, ... | |||
''' | |||
# 오버랩 테스트 | |||
durations = [51, 34, 52, 43, 46, 39, 52, 43] | |||
reds = [2, 2, 2, 2, 2, 2, 2, 2] | |||
yellows = [4, 4, 4, 4, 4, 4, 4, 4] | |||
offset = 5 | |||
# # 원본 | |||
# durations = [53, 33, 51, 43, 26, 60, 51, 43] | |||
# reds = [0, 0, 0, 0, 0, 0, 0, 0] | |||
# yellows = [4, 4, 4, 4, 4, 4, 4, 4] | |||
# offset = 0 | |||
# # 1795 (2023년 12월 20일 7시) | |||
# durations = [58, 29, 50, 43, 28, 59, 50, 43] | |||
# reds = [0, 0, 0, 0, 0, 0, 0, 0] | |||
# yellows = [4, 4, 4, 4, 4, 4, 4, 4] | |||
# offset = 0 | |||
# # 1888 (2023년 12월 21일 7시) | |||
# durations = [61, 28, 48, 43, 27, 62, 48, 43] | |||
# reds = [0, 0, 0, 0, 0, 0, 0, 0] | |||
# yellows = [4, 4, 4, 4, 4, 4, 4, 4] | |||
# offset = 0 | |||
# 각 현시의 이동류에 해당하는 signal state | |||
signal_states = [ | |||
'rrrrrrrrrGGGGrrrrrrrr', | |||
'rrrGrrrrrrrrrrrrrrrrr', | |||
'rrrrGGGrrrrrrrrrrrrrr', | |||
'rrrrrrrrrrrrrrrrrrrGG', | |||
'rrrrrrrrrrrrrGGrrrrrr', | |||
'GGGrrrrrrrrrrrrrrrrrr', | |||
'rrrrrrrGGrrrrrrrrrrrr', | |||
'rrrrrrrrrrrrrrrGGGGrr' | |||
] | |||
# 저장할 경로 | |||
path_xml = 'test.tll.xml' | |||
path_csv = 'test.tll.csv' | |||
# 노드id | |||
node_id = '11053' # 교차로번호: 5034 | |||
args = durations, reds, yellows, offset, signal_states, path_xml, node_id | |||
SetupTLSProgram(*args).main().to_csv(path_csv, index=False) | |||
print('hello') |
@ -1,33 +1,33 @@ | |||
,inter_no,start_hour,start_minute,dura_A1,dura_A2,dura_A3,dura_A4,dura_A5,dura_A6,dura_A7,dura_A8,dura_B1,dura_B2,dura_B3,dura_B4,dura_B5,dura_B6,dura_B7,dura_B8,cycle,offset | |||
0,175,00,00,37,39,55,29,0,0,0,0,37,39,25,59,0,0,0,0,160,57 | |||
1,175,07,00,40,42,55,33,0,0,0,0,40,42,29,59,0,0,0,0,170,40 | |||
2,175,09,00,43,45,55,37,0,0,0,0,43,45,33,59,0,0,0,0,180,28 | |||
3,175,18,30,46,48,55,41,0,0,0,0,46,48,37,59,0,0,0,0,190,18 | |||
4,176,00,00,37,73,40,0,0,0,0,0,37,73,40,0,0,0,0,0,150,131 | |||
5,176,07,00,37,93,40,0,0,0,0,0,37,93,40,0,0,0,0,0,170,153 | |||
6,176,09,00,37,103,40,0,0,0,0,0,37,103,40,0,0,0,0,0,180,169 | |||
7,176,18,30,37,113,40,0,0,0,0,0,37,113,40,0,0,0,0,0,190,185 | |||
8,177,00,00,36,20,68,26,0,0,0,0,36,20,68,26,0,0,0,0,150,35 | |||
9,177,07,00,40,25,71,34,0,0,0,0,40,25,71,34,0,0,0,0,170,33 | |||
10,177,09,00,43,27,70,40,0,0,0,0,43,27,70,40,0,0,0,0,180,41 | |||
11,177,18,30,45,32,77,36,0,0,0,0,45,32,77,36,0,0,0,0,190,49 | |||
12,178,00,00,38,39,40,23,0,0,0,0,38,39,40,23,0,0,0,0,140,50 | |||
13,178,07,00,38,39,42,41,0,0,0,0,38,39,62,21,0,0,0,0,160,90 | |||
14,178,09,00,38,39,43,50,0,0,0,0,38,39,71,22,0,0,0,0,170,80 | |||
15,178,18,30,38,39,44,59,0,0,0,0,38,39,80,23,0,0,0,0,180,75 | |||
16,201,00,00,24,24,17,58,17,0,0,0,24,24,17,58,17,0,0,0,140,133 | |||
17,201,07,00,30,36,18,58,18,0,0,0,30,36,18,58,18,0,0,0,160,132 | |||
18,201,09,00,33,36,25,58,18,0,0,0,33,36,25,58,18,0,0,0,170,134 | |||
19,201,18,30,36,50,18,58,18,0,0,0,36,50,18,58,18,0,0,0,180,137 | |||
20,202,00,00,39,101,0,0,0,0,0,0,39,101,0,0,0,0,0,0,140,103 | |||
21,202,07,00,46,114,0,0,0,0,0,0,46,114,0,0,0,0,0,0,160,103 | |||
22,202,09,00,46,114,0,0,0,0,0,0,46,114,0,0,0,0,0,0,160,103 | |||
23,202,18,30,48,122,0,0,0,0,0,0,48,122,0,0,0,0,0,0,170,103 | |||
24,206,00,00,33,35,26,26,0,0,0,0,33,35,26,26,0,0,0,0,120,10 | |||
25,206,07,00,44,44,26,26,0,0,0,0,44,44,26,26,0,0,0,0,140,7 | |||
26,206,09,00,45,53,26,26,0,0,0,0,45,53,26,26,0,0,0,0,150,17 | |||
27,206,18,30,46,62,26,26,0,0,0,0,46,62,26,26,0,0,0,0,160,10 | |||
28,210,00,00,43,29,56,22,0,0,0,0,24,48,56,22,0,0,0,0,150,115 | |||
29,210,07,00,43,39,65,23,0,0,0,0,24,58,65,23,0,0,0,0,170,131 | |||
30,210,09,00,43,43,70,24,0,0,0,0,28,58,70,24,0,0,0,0,180,137 | |||
31,210,18,30,43,47,75,25,0,0,0,0,24,66,75,25,0,0,0,0,190,143 | |||
,inter_no,start_hour,start_minute,dura_A1,dura_A2,dura_A3,dura_A4,dura_A5,dura_A6,dura_A7,dura_A8,dura_B1,dura_B2,dura_B3,dura_B4,dura_B5,dura_B6,dura_B7,dura_B8,cycle,offset,yellow_A1,yellow_B1,yellow_A2,yellow_B2,yellow_A3,yellow_B3,yellow_A4,yellow_B4,yellow_A5,yellow_B5,yellow_A6,yellow_B6,yellow_A7,yellow_B7,yellow_A8,yellow_B8,red_A1,red_B1,red_A2,red_B2,red_A3,red_B3,red_A4,red_B4,red_A5,red_B5,red_A6,red_B6,red_A7,red_B7,red_A8,red_B8 | |||
0,175,00,00,37,39,55,29,0,0,0,0,37,39,25,59,0,0,0,0,160,57,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
1,175,07,00,40,42,55,33,0,0,0,0,40,42,29,59,0,0,0,0,170,40,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
2,175,09,00,43,45,55,37,0,0,0,0,43,45,33,59,0,0,0,0,180,28,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
3,175,18,30,46,48,55,41,0,0,0,0,46,48,37,59,0,0,0,0,190,18,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
4,176,00,00,37,73,40,0,0,0,0,0,37,73,40,0,0,0,0,0,150,131,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
5,176,07,00,37,93,40,0,0,0,0,0,37,93,40,0,0,0,0,0,170,153,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
6,176,09,00,37,103,40,0,0,0,0,0,37,103,40,0,0,0,0,0,180,169,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
7,176,18,30,37,113,40,0,0,0,0,0,37,113,40,0,0,0,0,0,190,185,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
8,177,00,00,36,20,68,26,0,0,0,0,36,20,68,26,0,0,0,0,150,35,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
9,177,07,00,40,25,71,34,0,0,0,0,40,25,71,34,0,0,0,0,170,33,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
10,177,09,00,43,27,70,40,0,0,0,0,43,27,70,40,0,0,0,0,180,41,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
11,177,18,30,45,32,77,36,0,0,0,0,45,32,77,36,0,0,0,0,190,49,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
12,178,00,00,38,39,40,23,0,0,0,0,38,39,40,23,0,0,0,0,140,50,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
13,178,07,00,38,39,42,41,0,0,0,0,38,39,62,21,0,0,0,0,160,90,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
14,178,09,00,38,39,43,50,0,0,0,0,38,39,71,22,0,0,0,0,170,80,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
15,178,18,30,38,39,44,59,0,0,0,0,38,39,80,23,0,0,0,0,180,75,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
16,201,00,00,24,24,17,58,17,0,0,0,24,24,17,58,17,0,0,0,140,133,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
17,201,07,00,30,36,18,58,18,0,0,0,30,36,18,58,18,0,0,0,160,132,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
18,201,09,00,33,36,25,58,18,0,0,0,33,36,25,58,18,0,0,0,170,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
19,201,18,30,36,50,18,58,18,0,0,0,36,50,18,58,18,0,0,0,180,137,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
20,202,00,00,39,101,0,0,0,0,0,0,39,101,0,0,0,0,0,0,140,103,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
21,202,07,00,46,114,0,0,0,0,0,0,46,114,0,0,0,0,0,0,160,103,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
22,202,09,00,46,114,0,0,0,0,0,0,46,114,0,0,0,0,0,0,160,103,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
23,202,18,30,48,122,0,0,0,0,0,0,48,122,0,0,0,0,0,0,170,103,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
24,206,00,00,33,35,26,26,0,0,0,0,33,35,26,26,0,0,0,0,120,10,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
25,206,07,00,44,44,26,26,0,0,0,0,44,44,26,26,0,0,0,0,140,7,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
26,206,09,00,45,53,26,26,0,0,0,0,45,53,26,26,0,0,0,0,150,17,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
27,206,18,30,46,62,26,26,0,0,0,0,46,62,26,26,0,0,0,0,160,10,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 | |||
28,210,00,00,43,29,56,22,0,0,0,0,24,48,56,22,0,0,0,0,150,115,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
29,210,07,00,43,39,65,23,0,0,0,0,24,58,65,23,0,0,0,0,170,131,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
30,210,09,00,43,43,70,24,0,0,0,0,28,58,70,24,0,0,0,0,180,137,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 | |||
31,210,18,30,43,47,75,25,0,0,0,0,24,66,75,25,0,0,0,0,190,143,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 |
@ -0,0 +1,8 @@ | |||
김선중 대리 동부ICT에서 교차로 주기정보(주기별 현시별 녹색시간) 전송시 header에 시간정보가 있어서 해당 정보를 | |||
주기의 종료시간으로 활용해도 되는지 문의하였고 아래내용은 회신받은 내용입니다. 참고하세요 | |||
1) 메세지 header에서 제공하는 time은 신호제어서버의 정보 제공 시점의 시각입니다. | |||
2) 신호제어기에서 주기가 종료되면, 운영했던 SPLIT을 신호제어서버로 전송하고 | |||
신호제어서버는 데이터를 큐에 저장한 다음 1~2초 간격으로 묶어서 외부 서버에 제공할 예정입니다. | |||
3) 이런 경우 외부 서버에 SPLIT 정보 전송 지연이 최소 1~2초로 예상됩니다. | |||
4) 따라서 요청을 고려하여 전송 간격을 1초로 하면, 전송 지연을 최소화 할 수 있습니다. | |||
김선중 |
@ -0,0 +1,255 @@ | |||
☐ 교통신호제어센터의 교차로 신호정보 제공 방안 | |||
1. 개요 | |||
1) 본 문서는 성남시 신호제어서버에서 외부시스템에 신호정보 제공을 위한 인터페이 | |||
스를 정의한다. | |||
2) 신호정보제공 대상 외부시스템은 다음과 같다 | |||
- 디지털 트윈 서버 | |||
- 긴급차량관제 서버 | |||
- 스마트교차로 서버 | |||
- ITS 센터 | |||
3) 연계 구성 | |||
2. 통신 방식 | |||
1) 통신 방식 : TCP 소켓 통신 | |||
[보안정책상 UDP 소켓으로 변경여지 있습니다....] | |||
Server 측 Client 측 포트 규격 | |||
교통신호제어센터 외부시스템 7072 (TBD) TCP/IP | |||
2) Byte Ordering : Big-Endian | |||
- 최상위 바이트(MSB)를 먼저 보내고, 최하위 바이트(LSB)는 맨나중에 보냄 | |||
3) 데이터 형식 : Byte 형식 | |||
4) 문자 인코딩 : UTF-8 | |||
5) 포트번호 : 7072 | |||
3. 프레임 구조 | |||
1) 본 시스템의 프레임은 “HEADER + DATA“로 구성하며, ”HEADER“의 구성과 | |||
”COMMAND“ 목록은 다음과 같다. | |||
▪ HEADER 구성 (10 BYTES) | |||
항목 설명 데이터크기 데이터 유형 | |||
STX1 BYTE | |||
STX2 통신프레임의 시작 부호 1 (0x7E) 1 BYTE | |||
SEQUENCE BYTE | |||
TIME 통신프레임의 시작 부호 2 (0x7E) 1 BYTE | |||
COMMAND BYTE | |||
DATA LENGTH 순차번호 (0 ~ 255) 1 BYTE | |||
현재시각 (32BIT) 4 | |||
명령코드 1 | |||
데이터 프레임의 BYTE 길이 2 | |||
▪ COMMAND 목록 | |||
번호 COMMAND 설명 송수신 방향 비고 | |||
신호서버 → 외부서버 | |||
1 0xF2 교차로 신호운영현황 전송 신호서버 ← 외부서버 | |||
신호서버 → 외부서버 | |||
2 0xF3 교차로 신호운영현황 응답 (ACK) 신호서버 ← 외부서버 | |||
신호서버 → 외부서버 | |||
3 0xF4 교차로 주기정보 전송 신호서버 ← 외부서버 | |||
4 0xF5 교차로 주기정보 응답 (ACK) | |||
5 0xF6 교차로 DB 전송 | |||
6 0xF7 교차로 DB 응답 (ACK) | |||
4. 메시지 상세 구조 | |||
1) 교차로 신호운영현황 정보전송 | |||
▪ 1초 단위의 교차로 신호운영현황 정보 | |||
▪ 정보전송 “교차로 시작번호”부터 N개의 교차로를 연속적으로 전송한다 | |||
BYTE 항목 BIT 설명 | |||
1 교차로 | |||
2 16 BIT 정보전송 교차로 시작번호 (1 – 9999) | |||
3 번호 | |||
4 현시 75 RING A의 PHASE (0 ∼ 7) | |||
코드 40 RING A의 STEP (0 ∼ 31) | |||
5 75 RING B의 PHASE (0 ∼ 7) | |||
제어기 40 RING B의 STEP (0 ∼ 31) | |||
6 운영상태 센터 통신 FAIL 상태 1 : FAIL, 0 : 정상 | |||
7 | |||
7 제어기 6 운용 맵번호 0 : 일반제, 1~5 : 시차제, 6 : 전용맵 | |||
8 상태 5 | |||
9 4 등기종류 1 : 4색등, 0 : 3색등 | |||
10 3 | |||
11 교통신호기 운영모드 0 : SCU 고정주기 모드 | |||
- 2 1 : 감응하지 않는 OFFLINE 제어모드 | |||
18 RING 운영방식 2 : 감응되는 OFFLINE 제어모드 | |||
19 1 현시유지 4 : 감응되는 온라인 제어모드 | |||
- 우선신호 5 : 감응하지 않는 온라인 제어모드 | |||
26 0 전이 | |||
7 감응 1 : DUAL-RING, 0 : SINGLE-RING | |||
6 소등 1 : ON, 0 : OFF | |||
5 점멸 1 : 서비스 중, 0 : OFF | |||
4 수동 1 : 전이 중, 0 : OFF | |||
3 주기 COUNT 1 : 감응, 0 : 정상 | |||
2 현 주기 1 : 소등, 0 : 정상 | |||
1 연동 1 : 점멸, 0 : 정상 | |||
0 예비 1 : 수동, 0 : 정상 | |||
- 0 ~ 255 초 | |||
- 0 ~ 255 초 | |||
- 실제 계측 OFFSET | |||
- | |||
교차로 시작번호+1 신호운영정보 (8 Bytes) | |||
교차로 시작번호+2 신호운영정보 (8 Bytes) | |||
... | |||
... 교차로 시작번호+N 신호운영정보 (8 Bytes) | |||
-> A링, B링의 이동류번호 제공 필요. 시차제가 적용될 경우 교차로 DB정보를 통해 | |||
서는 파악이 불가능하며, 신호운영현황을 통해 제공되는 이동류번호로만 파악이 | |||
가능함 | |||
2) 교차로 주기정보 : 주기 종료 후 Split 정보 제공 | |||
▪ 주기 종료 후 Ring-A, Ring-B의 운영한 현시정보를 전송한다. | |||
▪ 정보전송은 N개의 교차로 정보를 연속하여 전송이 가능하다 | |||
BYTE 항목 BIT 설명 | |||
교차로 번호 | |||
1 16 BIT 정보전송 교차로번호 (1 – 9999) | |||
2 | |||
3 RING-A 운영시간 8 Bytes PHASE 1 – 8 | |||
- | |||
10 RING-B 운영시간 8 Bytes PHASE 1 – 8 | |||
11 | |||
- | |||
18 | |||
2번째 교차로 번호/ Ring-A / RING-B 운영시간 (18 Bytes) | |||
3번째 교차로 번호/ Ring-A / RING-B 운영시간 (18 Bytes) | |||
4번째 교차로 번호/ Ring-A / RING-B 운영시간 (18 Bytes) | |||
.... | |||
3) 교차로 DB 정보 : 교차로 DB 변경시 제공 (JSON 표기) | |||
WEEK PLAN JSON 표기 WEEK PLAN data 설명 | |||
{ data : 7 plan_no | |||
“lcid” : number, plan_no = 1 ~ 10 | |||
“type” : “weekplan”, | |||
“data” : number[], | |||
} | |||
DAYPLAN JSON 표기 DAYPLAN data 설명 | |||
{ plan_no = 1 ~ 10 | |||
“lcid” : number, [hour, min, cycle, offset, split[16]] * 16 | |||
“type” : “dayplan”, | |||
“plan” : { | |||
“plan_no”: number, | |||
“data”: number[], | |||
}[] | |||
} | |||
HOLIDAY PLAN JSON 표기 HOLIDAY PLAN data 설명 | |||
{ | |||
“lcid”: number, [month, day, plan_no] * 30 | |||
“type”: “holidayplan”, plan_no = 1 ~ 10 | |||
“data”: number[], | |||
} | |||
SIGNAL MAP JSON 표기 SIGNAL MAP data 설명 | |||
{ | |||
“lcid”: number, map_no = 1 - 6 | |||
“type”: “signal_map”, [output[16], min, max, eop] * 32 step | |||
“data”: { | |||
“map_no” : number, | |||
“a_ring” : number[], | |||
“b_ring” : number[], | |||
}[], | |||
} | |||
이동류 번호, 방위각 JSON 표기 기반정보 구성 data 설명 | |||
{ | |||
“lcid”: number, map_no = 1 - 6 | |||
“type”: “geo_map”, [direction(이동류 번호), angle(방위각)] * 8현시 | |||
“data”: { | |||
“map_no” : number, | |||
“a_ring” : number[], | |||
“b_ring” : number[], | |||
}[], | |||
} | |||
→ dayplan data에 yellow time, red time, 주현시 정보 추가 필요 | |||
① 교차로 WEEK PLAN | |||
② 교차로 DAY PLAN | |||
③ 교차로 HOLIDAY PLAN | |||
④ 시그널맵 (Ring-A , Ring-B) | |||
⑤ 교차로 기반정보 구성 (이동류번호, 방위각) | |||
RING 현시 1 현시 2 현시 3 현시 4 현시 5 현시 6 현시 7 현시 8 | |||
이동류번호, | |||
A-ring | |||
방위각 | |||
이동류번호, | |||
B-ring | |||
방위각 | |||
-> 방위각 정보는 진입, 진출 모두 제공 필요 | |||
⑥ etc. | |||
-> 교차로 ID, 이름 및 위-경도 필요. sumo 네트워크 노드와 교차로 ID를 매핑하기 | |||
위해서 필요 | |||
▪ 이동류 번호 (1 byte) | |||
이동류 이동류 이동류 이동류 이동류 이동류 | |||
설명 | |||
설명 설명 | |||
번호 방향 | |||
번호 방향 번호 방향 | |||
1 좌회전 (동 → 남) 9 좌회전 (북동 → 남동) 17 ˙ 보행신호 | |||
2 직진 (서 → 동) 10 직진 (남서 → 북동) 18 ˙ 신호 없음 | |||
3 좌회전 (남 → 서) 11 좌회전 (남동 → 남서) 21 ˙ 신호우회전 | |||
4 직진 (북 → 남) 12 직진 (북서 → 남동) | |||
5 좌회전 (서 → 북) 13 좌회전 (남서 → 북서) | |||
6 직진 (동 → 서) 14 직진 (북동 → 남서) | |||
7 좌회전 (북 → 동) 15 좌회전 (북서 → 북동) | |||
8 직진 (남 → 북) 16 직진 (남동 → 북서) | |||
▪ 방위각 (2 bytes) : 0 ~ 360도 | |||
▪ 화살표 좌표 (24 bytes) | |||
항목 설명 | |||
이동류 위도 DD.DDDDDD | |||
(화살표 시작 좌표) 경도 DDD.DDDDDD | |||
위도 DD.DDDDDD | |||
이동류 경도 DDD.DDDDDD | |||
(화살표 중간점 좌표) 위도 DD.DDDDDD | |||
경도 DDD.DDDDDD | |||
이동류 | |||
(화살표 종료점 좌표) | |||
@ -0,0 +1,322 @@ | |||
☐ 교통신호제어센터의 교차로 신호정보 제공 방안 | |||
1. 개요 | |||
1) 본 문서는 성남시 신호제어서버에서 ITS서버에 신호정보 제공을 위한 인터페이스 | |||
를 정의한다. | |||
2) 연계 구성 | |||
2. 통신 방식 | |||
1) 통신 방식 : UDP 소켓 통신 | |||
Server 측 Client 측 포트 규격 | |||
교통신호제어센터 외부시스템 7072 (TBD) UDP | |||
2) Byte Ordering : Big-Endian | |||
- 최상위 바이트(MSB)를 먼저 보내고, 최하위 바이트(LSB)는 맨나중에 보냄 | |||
3) 데이터 형식 : Byte 형식 | |||
4) 문자 인코딩 : UTF-8 | |||
5) 포트번호 : 7072 | |||
3. 프레임 구조 | |||
1) 본 시스템의 프레임은 “HEADER + DATA“로 구성하며, ”HEADER“의 구성과 | |||
”COMMAND“ 목록은 다음과 같다. | |||
▪ HEADER 구성 (10 BYTES) | |||
항목 설명 데이터크기 데이터 유형 | |||
STX1 BYTE | |||
STX2 통신프레임의 시작 부호 1 (0x7E) 1 BYTE | |||
SEQUENCE BYTE | |||
TIME 통신프레임의 시작 부호 2 (0x7E) 1 BYTE | |||
COMMAND BYTE | |||
DATA LENGTH 순차번호 (0 ~ 255) 1 BYTE | |||
현재시각 (32BIT) 4 | |||
명령코드 1 | |||
데이터 프레임의 BYTE 길이 2 | |||
▪ COMMAND 목록 | |||
번호 COMMAND 설명 송수신 방향 비고 | |||
예비 | |||
1 0xF0 교차로 신호운영현황 전송-1 신호서버 → 외부서버 예비 | |||
교차로 신호운영현황 응답 (ACK) 신호서버 ← 외부서버 | |||
2 0xF1 신호서버 → 외부서버 예비 | |||
교차로 신호운영현황 전송-2 신호서버 ← 외부서버 | |||
3 0xF2 교차로 신호운영현황 응답 (ACK) 신호서버 → 외부서버 | |||
신호서버 ← 외부서버 | |||
4 0xF3 교차로 주기정보 전송 신호서버 → 외부서버 | |||
교차로 주기정보 응답 (ACK) 신호서버 ← 외부서버 | |||
5 0xF4 신호서버 ← 외부서버 | |||
교차로 DB 전송 | |||
6 0xF5 교차로 DB 응답 (ACK) | |||
7 0xF6 신호정보제공 요청 | |||
8 0xF7 | |||
9 0xF9 | |||
▪ 전체 메시지 구성 (HEADER + DATA) | |||
HEADER DATA | |||
DATA | |||
STX1 STX2 SEQ. TIME COMMAND LENGTH [Length] Bytes | |||
1 byte 1 byte 1 byte 2 byte | |||
1 byte 4 byte | |||
4. 데이터 상세 구조 | |||
1) 교차로 신호운영현황 정보 전송-1 | |||
▪ 1초 단위로 교차로 신호운영현황을 최소 정보만 요약하여 전송한다. | |||
▪ 전체 교차로를 한번에 전송한다 | |||
예) 800 (교차로 수) x 3 bytes = 2400 bytes 전송 | |||
BYTE 항목 BIT 설명 | |||
1 현시 | |||
2 코드 75 RING A의 PHASE (0 ∼ 7) | |||
40 | |||
3 제어기 75 RING A의 이동류번호 (1 ∼ 21) | |||
운영상태 40 | |||
4 RING B의 PHASE (0 ∼ 7) | |||
- 7 | |||
6 6 RING B의 이동류번호 (1 ∼ 21) | |||
7 5 | |||
- 4 제어기 통신상태 1 : 통신 FAIL, 0 : 정상 | |||
9 3 | |||
2 모순 상태 1 : 모순, 0 : 정상 | |||
1 소등 상태 1 : 소등, 0 : 정상 | |||
0 점멸 상태 1 : 점멸, 0 : 정상 | |||
수동 상태 1 : 수동, 0 : 정상 | |||
교통신호기 운영모드 0 : SCU 고정주기 모드 | |||
1 : 감응하지 않는 OFFLINE 제어모드 | |||
2 : 감응되는 OFFLINE 제어모드 | |||
4 : 감응되는 온라인 제어모드 | |||
5 : 감응하지 않는 온라인 제어모드 | |||
2번 교차로 신호운영정보 (3 Bytes) | |||
2번 교차로 신호운영정보 (3 Bytes) | |||
- ... | |||
- N번 교차로 신호운영정보 (3 Bytes) | |||
2) 교차로 신호운영현황 정보 전송-2 | |||
▪ 1초 단위의 교차로 신호운영현황 정보 | |||
▪ 정보전송 “교차로 시작번호”부터 N개의 교차로를 연속적으로 전송한다 | |||
BYTE 항목 BIT 설명 | |||
1 | |||
2 교차로 16 BIT 정보전송 교차로 시작번호 (1 – 9999) | |||
3 번호 | |||
4 현시 75 RING A의 PHASE (0 ∼ 7) 1 : 통신 FAIL, 0 : 정상 | |||
코드 40 RING A의 STEP (0 ∼ 31) | |||
5 75 RING B의 PHASE (0 ∼ 7) | |||
제어기 40 RING B의 STEP (0 ∼ 31) | |||
6 운영상태 제어기 통신상태 | |||
7 | |||
7 제어기 6 운용 맵번호 0 : 일반제, 1~5 : 시차제, 6 : 전용맵 | |||
8 상태 5 | |||
9 4 교차로 연등 0 : 일반교차로, 1 : 연등교차로 | |||
10 3 | |||
11 교통신호기 운영모드 0 : SCU 고정주기 모드 | |||
12 2 1 : 감응하지 않는 OFFLINE 제어모드 | |||
- POLICE PANEL 수동진행 SW 2 : 감응되는 OFFLINE 제어모드 | |||
20 1 POLICE PANEL 수동 SW 4 : 감응되는 온라인 제어모드 | |||
21 POLICE PANEL 점멸 SW 5 : 감응하지 않는 온라인 제어모드 | |||
- 0 POLICE PANEL 소등 SW | |||
29 7 모순 상태 1 : ON, 0 : OFF | |||
6 소등 상태 1 : ON, 0 : OFF | |||
5 점멸 상태 1 : ON, 0 : OFF | |||
4 데이터베이스 상태 1 : ON, 0 : OFF | |||
3 주기 COUNT 1 : 모순, 0 : 정상 | |||
2 현 주기 1 : 소등, 0 : 정상 | |||
1 연동 1 : 점멸, 0 : 정상 | |||
0 A-ring 이동류번호 1 : 이상, 0 : 정상 | |||
- B-ring 이동류번호 0 ~ 255 초 | |||
- 0 ~ 255 초 | |||
- 실제 계측 OFFSET | |||
- 1 – 21 (보행자 포함여부 ?) | |||
- 1 – 21 (보행자 포함여부 ?) | |||
교차로 시작번호+1 신호운영정보 (9 Bytes) | |||
교차로 시작번호+2 신호운영정보 (9 Bytes) | |||
... | |||
... 교차로 시작번호+N 신호운영정보 (9 Bytes) | |||
3) 교차로 주기정보 : 주기 종료 후 Split 정보 제공 | |||
▪ 주기 종료 후 Ring-A, Ring-B의 운영한 현시정보를 전송한다. | |||
▪ 정보전송은 N개의 교차로 정보를 연속하여 전송이 가능하다 | |||
BYTE 항목 BIT 설명 | |||
교차로 번호 16 BIT 정보전송 교차로번호 (1 – 9999) | |||
1 | |||
2 RING-A 운영시간 8 Bytes PHASE 1 – 8 | |||
3 | |||
- RING-B 운영시간 8 Bytes PHASE 1 – 8 | |||
10 | |||
11 | |||
- | |||
18 | |||
2번째 교차로 번호/ Ring-A / RING-B 운영시간 (18 Bytes) | |||
3번째 교차로 번호/ Ring-A / RING-B 운영시간 (18 Bytes) | |||
4번째 교차로 번호/ Ring-A / RING-B 운영시간 (18 Bytes) | |||
.... | |||
4) 교차로 DB 정보 : 교차로 DB 변경시 제공 (JSON 표기) | |||
① 교차로 WEEK PLAN | |||
∎ JSON 표기 | |||
WEEK PLAN JSON 표기 WEEK PLAN data 설명 | |||
{ data : 7 plan_no | |||
“lcid” : number, plan_no = 1 ~ 10 | |||
“type” : “weekplan”, | |||
“data” : number[], | |||
} | |||
∎ week plan table 정의 | |||
② 교차로 DAY PLAN DAYPLAN data 설명 | |||
∎ JSON 표기 plan_no = 1 ~ 10 | |||
time : [A_flow_no, A_red_time, A_yellow_time, A_min_time, | |||
DAYPLAN JSON 표기 | |||
B_flow_no, B_red_time, B_yellow_time, B_min_time] | |||
{ data : [hour, min, cycle, offset, split[16]] | |||
“lcid” : number, | |||
“type” : “dayplan”, | |||
“plan” : { | |||
“plan_no”: number, | |||
“time” : number[], | |||
“data” : number[], | |||
}[] | |||
} | |||
∎ FLOW/RED/YELLOW | |||
현시 이동류번호 A-ring yellow_time 이동류번호 B-ring | |||
red_time red_time yellow_time | |||
현시 1 | |||
현시 2 | |||
현시 3 | |||
현시 4 | |||
현시 5 | |||
현시 6 | |||
현시 7 | |||
현시 8 | |||
∎ DAY PLAN TIME Table | |||
③ 교차로 HOLIDAY PLAN HOLIDAY PLAN data 설명 | |||
∎ JSON 표기 | |||
[month, day, plan_no] * 30 | |||
HOLIDAY PLAN JSON 표기 plan_no = 1 ~ 10 | |||
{ | |||
“lcid”: number, | |||
“type”: “holidayplan”, | |||
“data”: number[], | |||
} | |||
∎ HOLIDAY Plan Table | |||
④ SIGNAL MAP (Ring-A , Ring-B) SIGNAL MAP data 설명 | |||
∎ JSON 표기 map_no = 1 - 6 | |||
[output[16], min, max, eop] * 32 step | |||
SIGNAL MAP JSON 표기 | |||
{ | |||
“lcid”: number, | |||
“type”: “signal_map”, | |||
“data”: { | |||
“map_no” : number, | |||
“a_ring” : number[], | |||
“b_ring” : number[], | |||
}[], | |||
} | |||
∎ SIGNAL MAP Table | |||
⑤ 교차로 기반정보 구성 | |||
∎ JSON 표기 | |||
교차로 기반정보 구성 JSON 표기 교차로 기반정보 구성 data 설명 | |||
`{ | |||
“lcid”: number, | |||
“type”: “geo_map”, | |||
“intName”: string, // 교차로명 | |||
“intType”: number, // 교차로유형 (0: 주교차로, 1: 연등교차로) | |||
“lcType”: number, // 제어기 유형 (1: 2004년형, 2: 2010 년형, 3: 2023년형) | |||
“lampType”: number, // 등기 유형 (1: 4색등, 2: 3색등) | |||
“mainIntNo”: number, // 연등교차로인 경우 주교차로 번호 | |||
“groupNo”: number, // 그룹번호 | |||
“nodeNo”: number, // 교차로 노드 ID | |||
“intLat”: number, // 교차로 위도 (ex : 36.123500) | |||
“intLng”: number, // 교차로 경도 (ex : 127.123500) | |||
“ppcType”: number, // PPC 유형 (0 : 설치안됨, 1 : 현장방식, 2 : 중앙제어방식) | |||
“mainP”: number, // 주현시번호 (1 – 8) | |||
“flow”: { | |||
“num”: number, // 이동류 번호 | |||
“geoXY: number[], // 화살표 시작점 위도/경도, 중간점 위도/경도, 종료점 위도/경도, | |||
}[] | |||
} | |||
▪ 이동류 번호 | |||
이동류 이동류 이동류 이동류 이동류 이동류 | |||
설명 | |||
설명 설명 | |||
번호 방향 | |||
번호 방향 번호 방향 | |||
1 좌회전 (동 → 남) 9 좌회전 (북동 → 남동) 17 ˙ 보행신호 | |||
2 직진 (서 → 동) 10 직진 (남서 → 북동) 18 ˙ 신호 없음 | |||
3 좌회전 (남 → 서) 11 좌회전 (남동 → 남서) 21 ˙ 신호우회전 | |||
4 직진 (북 → 남) 12 직진 (북서 → 남동) | |||
5 좌회전 (서 → 북) 13 좌회전 (남서 → 북서) | |||
6 직진 (동 → 서) 14 직진 (북동 → 남서) | |||
7 좌회전 (북 → 동) 15 좌회전 (북서 → 북동) | |||
8 직진 (남 → 북) 16 직진 (남동 → 북서) | |||
@ -1,876 +0,0 @@ | |||
# python .\Scripts\generate_signals.py | |||
import pandas as pd | |||
import numpy as np | |||
import os, sys | |||
import json | |||
import copy | |||
from tqdm import tqdm | |||
import sumolib, traci | |||
from datetime import datetime | |||
import time | |||
class SignalGenerator(): | |||
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 = [] | |||
self.midnight = int(datetime(2024, 1, 5, 0, 0, 0).timestamp()) | |||
self.next_day = int(datetime(2024, 1, 6, 0, 0, 0).timestamp()) | |||
self.fsecs = range(self.midnight, self.next_day, 5) # fsecs : unix time by Five SECondS | |||
self.fmins = range(self.midnight, self.next_day, 300) # fmins : unix time by Five MINuteS | |||
self.present_time = datetime.now().replace(month=1, day=5, hour=10).timestamp() | |||
self.present_time = max([fmin for fmin in list(self.fmins) if fmin <= self.present_time]) | |||
self.adder = 600 # 10분 : '현재시점 + 10분'에 가상신호를 생성하기 위함. | |||
self.subtractor = 1800 # 30분 : '현재시점 - 30분'의 신호이력을 가져온다. | |||
# 1. 데이터 준비 | |||
def prepare_data(self): | |||
print("1. 데이터를 준비합니다.") | |||
self.load_networks() | |||
self.time11 = datetime.now() | |||
self.load_tables() | |||
self.time12 = datetime.now() | |||
# self.check_networks() | |||
self.time13 = datetime.now() | |||
# self.check_tables() | |||
self.time14 = datetime.now() | |||
self.prepare_auxiliaries() | |||
self.time15 = datetime.now() | |||
# 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.plan = pd.read_csv(os.path.join(self.path_tables, 'plan.csv'), dtype=loading_dtype) | |||
self.history = pd.read_csv(os.path.join(self.path_tables, 'history.csv'), dtype=loading_dtype).sort_values(by='end_unix') | |||
self.inter_node = pd.read_csv(os.path.join(self.path_tables, 'inter_node.csv'), dtype=loading_dtype) | |||
self.matching = pd.read_csv(os.path.join(self.path_intermediates, 'matching.csv'), dtype=loading_dtype) | |||
self.match1 = pd.read_csv(os.path.join(self.path_intermediates, 'match1.csv'), dtype=loading_dtype) | |||
self.match6 = pd.read_csv(os.path.join(self.path_intermediates, 'match6.csv'), dtype=loading_dtype) | |||
self.match6 = self.match6[['node_id', 'phase_no', 'ring_type', 'inc_edge', 'out_edge']].reset_index(drop=True) | |||
self.plan_set = self.plan.set_index(['inter_no','start_hour','start_minute']) | |||
# 교차로목록 정의 | |||
self.inter_nos = sorted(self.inter_info.inter_no.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_history() | |||
# 교차로정보, 방위각정보, 신호계획에 대해서는 preprocess_daily.py에서 | |||
# 무결성검사를 완료했으므로 여기에서는 따로 검사하지 않음. | |||
# self.check_moves() # 이동류번호에 대한 무결성검사 필요하나 아직 작성하지 않음. (24. 2. 5 화) | |||
print("1-4. 테이블들의 무결성 검사를 완료했습니다.") | |||
# 1-4-1. 신호이력(history) 검사 | |||
def check_history(self): | |||
# 1-4-1-1. inter_no 검사 | |||
# self.history.loc[0, 'inter_no'] = '4' # 에러 발생을 위한 코드 | |||
missing_inter_nos = set(self.history.inter_no) - set(self.inter_nos) | |||
if missing_inter_nos: | |||
msg = f"1-4-1-1. history의 inter_no 중 교차로 목록(inter_nos)에 포함되지 않는 항목이 있습니다: {missing_inter_nos}" | |||
self.issues.append(msg) | |||
# 1-4-1-2. 종료유닉스 검사 | |||
# self.history.loc[0, 'end_unix'] = 38.0 # 에러 발생을 위한 코드 | |||
self.min_unix, self.max_unix = int(datetime(2020, 1, 1).timestamp()), int(datetime(2038, 1, 1).timestamp()) | |||
for row in self.history.itertuples(index=True): | |||
unixbool = self.min_unix <= row['end_unix'] <= self.max_unix | |||
if not unixbool: | |||
msg = f"1-4-1-2. 적정 범위를 벗어난 유닉스시각(end_unix)이 존재합니다 : inter_no : {row['inter_no']}" | |||
self.issues.append(msg) | |||
# 1-4-1-3. 현시시간 검사 | |||
# self.history.loc[0, 'dura_A1'] = -2 # 에러 발생을 위한 코드 | |||
durations = self.history[[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.history[~ valid_indices].inter_no.unique()) | |||
if invalid_inter_nos: | |||
msg = f"1-4-1-3. 음수이거나 200보다 큰 현시시간이 존재합니다. : {invalid_inter_nos}" | |||
# 1-5. 보조 딕셔너리, 데이터프레임, 리스트 등 만들기 | |||
def prepare_auxiliaries(self): | |||
# inter2node : a dictionary that maps inter_no to the node_id | |||
inter_node_p = self.inter_node[self.inter_node.inter_type=='parent'] | |||
self.inter2node = dict(zip(inter_node_p['inter_no'], inter_node_p['node_id'])) | |||
self.node2inter = dict(zip(self.inter_node['node_id'], self.inter_node['inter_no'])) | |||
# split, isplit : A,B 분리 혹은 통합시 사용될 수 있는 딕셔너리 | |||
self.splits = {} # splits maps (inter_no, start_hour, start_minute) to split | |||
for i, row in self.plan.iterrows(): | |||
inter_no = row.inter_no | |||
start_hour = row.start_hour | |||
start_minute = row.start_minute | |||
cycle = row.cycle | |||
dura_A = np.array(row[[f'dura_A{j}' for j in range(1, 9)]]) | |||
dura_B = np.array(row[[f'dura_B{j}' for j in range(1, 9)]]) | |||
cums_A = dura_A.cumsum() | |||
cums_B = dura_B.cumsum() | |||
combined_row = np.unique(np.concatenate((cums_A,cums_B))) | |||
detailed_durations = np.concatenate(([combined_row[0]], np.diff(combined_row))) | |||
self.splits[(inter_no, start_hour, start_minute)] = {} # split maps (phas_A, phas_B) to k | |||
ja = 0 | |||
jb = 0 | |||
for k in range(len(detailed_durations)): | |||
dura_A[ja] -= detailed_durations[k] | |||
dura_B[jb] -= detailed_durations[k] | |||
self.splits[(inter_no, start_hour, start_minute)][(ja+1, jb+1)] = k+1 | |||
if dura_A[ja] == 0: | |||
ja += 1 | |||
if dura_B[jb] == 0: | |||
jb += 1 | |||
self.isplits = {} # the inverse of splits | |||
for i in self.splits: | |||
self.isplits[i] = {self.splits[i][k]:k for k in self.splits[i]} # isplit maps k to (phas_A, phas_B) | |||
# timetable : 교차로별 프로그램 시작시각 | |||
self.timetable = self.plan[['start_hour', 'start_minute']].drop_duplicates() | |||
self.timetable['start_seconds'] = self.midnight + self.timetable['start_hour'] * 3600 + self.timetable['start_minute'] * 60 | |||
# A dictionary that maps parent_id to a list of child_ids | |||
self.pa2ch = {'i0':['u00'], 'i1':[], 'i2':['u20'], 'i3':['c30', 'u30', 'u31', 'u32'], 'i6':['u60'], 'i7':[], 'i8':[], 'i9':[]} | |||
self.node_ids = sorted(self.inter_node.node_id.unique()) | |||
self.parent_ids = sorted(self.inter_node[self.inter_node.inter_type=='parent'].node_id.unique()) | |||
self.nodes = [self.net.getNode(node_id) for node_id in self.node_ids] | |||
# node2num_cycles : A dictionary that maps a node_id to the number of cycles | |||
with open(os.path.join(self.path_intermediates, 'node2num_cycles.json'), 'r') as file: | |||
# json.load() 함수를 사용해 파일 내용을 Python 딕셔너리로 불러옵니다. | |||
self.node2num_cycles = json.load(file) | |||
# 2. 신호이력 전처리 | |||
def process_history(self): | |||
print("2. 신호이력 테이블을 변환합니다.") | |||
self.make_rhistory() | |||
self.time21 = datetime.now() | |||
self.make_rhists() | |||
self.time22 = datetime.now() | |||
self.make_hrhists() | |||
self.time23 = datetime.now() | |||
# 2-1. rhistory | |||
def make_rhistory(self): | |||
# 1. 조회시점의 유닉스 타임 이전의 신호이력 수집 | |||
self.rhistory = self.history.copy() # recent history | |||
self.rhistory = self.rhistory[(self.rhistory.end_unix <= self.present_time) & (self.rhistory.end_unix > self.present_time - self.subtractor)] | |||
# rhistory에 모든 교차로번호가 존재하지 않으면 해당 교차로번호에 대한 신호이력을 추가함 (at 최근 프로그램 시작시각) | |||
whole_inter_nos = set(self.history.inter_no.unique()) | |||
recent_inter_nos = set(self.rhistory.inter_no.unique()) | |||
if not whole_inter_nos==recent_inter_nos: | |||
for inter_no in whole_inter_nos - recent_inter_nos: | |||
program_start, prow = self.load_prow(inter_no, self.present_time - self.subtractor) | |||
cycle = prow.cycle.iloc[0] | |||
row1 = prow.copy() | |||
row2 = prow.copy() | |||
# prow에서 필요한 부분을 rhistory에 추가 | |||
row1['end_unix'] = program_start | |||
row2['end_unix'] = program_start + cycle | |||
self.rhistory = pd.concat([self.rhistory, row1, row2])#.reset_index(drop=True) | |||
# present_time + adder 의 시각에 한 주기의 신호 추가 | |||
for inter_no in set(whole_inter_nos): | |||
program_start, prow = self.load_prow(inter_no, self.present_time) | |||
cycle = prow.cycle.iloc[0] | |||
row3 = prow.copy() | |||
# prow에서 필요한 부분을 rhistory에 추가 | |||
row3['end_unix'] = self.present_time + self.adder | |||
self.rhistory = pd.concat([self.rhistory, row3])#.reset_index(drop=True) | |||
# 2. 시작 유닉스 타임컬럼 생성 후 종류 유닉스 타임에서 현시별 현시기간 컬럼의 합을 뺀 값으로 입력 | |||
# - 현시시간의 합을 뺀 시간의 +- 10초 이내에 이전 주기정보가 존재하면 그 유닉스 시간을 시작 유닉스시간 값으로 하고, 존재하지 않으면 현시시간의 합을 뺀 유닉스 시간을 시작 유닉스 시간으로 지정 | |||
for i, row in self.rhistory.iterrows(): | |||
inter_no = row.inter_no | |||
end_unix = row.end_unix | |||
elapsed_time = row[[f'dura_{alph}{j}' for alph in ['A', 'B'] for j in range(1,9)]].sum() // 2 # 현시시간 합 | |||
# 이전 유닉스 존재하지 않음 : 현시시간 합의 차 | |||
start_unix = end_unix - elapsed_time | |||
pre_rows = self.history[:i] # previous rows | |||
if inter_no in pre_rows.inter_no.unique(): # 이전 유닉스 존재 | |||
pre_unix = pre_rows[pre_rows.inter_no == inter_no]['end_unix'].iloc[-1] # previous unix time | |||
# 이전 유닉스 존재, abs < 10 : 이전 유닉스 | |||
if abs(pre_unix - start_unix) < 10: | |||
start_unix = pre_unix | |||
# 이전 유닉스 존재, abs >=10 : 현시시간 합의 차 | |||
else: | |||
pass | |||
self.rhistory.loc[i, 'start_unix'] = start_unix | |||
self.rhistory[self.rhistory.isna()] = 0 | |||
self.rhistory['start_unix'] = self.rhistory['start_unix'].astype(int) | |||
self.rhistory = self.rhistory[['inter_no', 'start_unix'] + [f'dura_{alph}{j}' for alph in ['A', 'B'] for j in range(1,9)] + ['cycle']] | |||
def load_prow(self, inter_no, time): | |||
''' | |||
load planned row | |||
''' | |||
# 프로그램 시작시각 | |||
program_starts = np.array(self.timetable.start_seconds) | |||
idx = (program_starts <= time).sum() - 1 | |||
program_start = program_starts[idx] | |||
# 최근 프로그램 시작시각에 대한 신호계획 | |||
start_hour = self.timetable.iloc[idx].start_hour | |||
start_minute = self.timetable.iloc[idx].start_minute | |||
# prow = self.plan[(self.plan.inter_no==inter_no) & (self.plan.start_hour==start_hour) & (self.plan.start_minute==start_minute)] # planned row | |||
prow = self.plan_set.loc[(inter_no, start_hour, start_minute)] | |||
prow = pd.DataFrame([prow],index=[0]) | |||
prow['inter_no'] = inter_no | |||
return program_start, prow | |||
# 2-2. rhists | |||
def make_rhists(self): | |||
self.rhists = [] | |||
for inter_no in self.rhistory.inter_no.unique(): | |||
self.rhist = self.rhistory.copy()[self.rhistory.inter_no==inter_no] | |||
self.rhist = self.rhist.drop_duplicates(subset=['start_unix']).reset_index(drop=True) | |||
# D_n 및 S_n 값 정의 | |||
self.rhist['D_n'] = 0 # D_n : 시간차이 | |||
self.rhist['S_n'] = 0 # S_n : 현시시간합 | |||
for n in range(len(self.rhist)): | |||
curr_unix = self.rhist.iloc[n].start_unix # current start_unix | |||
self.rhist.loc[n, ['D_n', 'S_n']] = self.calculate_DS(self.rhist, curr_unix) | |||
# 이전시각, 현재시각 | |||
prev_unix = self.rhist.loc[0, 'start_unix'] # previous start_unix | |||
curr_unix = self.rhist.loc[1, 'start_unix'] # current start_unix | |||
# rhist의 마지막 행에 도달할 때까지 반복 | |||
while True: | |||
n = self.rhist[self.rhist.start_unix==curr_unix].index[0] | |||
cycle = self.rhist.loc[n, 'cycle'] | |||
D_n = self.rhist.loc[n, 'D_n'] | |||
S_n = self.rhist.loc[n, 'S_n'] | |||
# 참값인 경우 | |||
if (abs(D_n - S_n) <= 5): | |||
pass | |||
# 참값이 아닌 경우 | |||
else: | |||
# 2-1-1. 결측치 처리 : 인접한 두 start_unix의 차이가 계획된 주기의 두 배보다 크면 결측이 일어났다고 판단, 신호계획의 현시시간으로 "대체" | |||
if curr_unix - prev_unix >= 2 * cycle: | |||
# prev_unix를 계획된 주기만큼 늘려가면서 한 행씩 채워나간다. | |||
# (curr_unix와의 차이가 계획된 주기보다 작거나 같아질 때까지) | |||
while curr_unix - prev_unix > cycle: | |||
prev_unix += cycle | |||
# 신호 계획(prow) 불러오기 | |||
start_seconds = np.array(self.timetable.start_seconds) | |||
idx = (start_seconds <= prev_unix).sum() - 1 | |||
start_hour = self.timetable.iloc[idx].start_hour | |||
start_minute = self.timetable.iloc[idx].start_minute | |||
prow = self.plan.copy()[(self.plan.inter_no==inter_no) & (self.plan.start_hour==start_hour) & (self.plan.start_minute==start_minute)] # planned row | |||
# prow에서 필요한 부분을 rhist에 추가 | |||
prow['start_unix'] = prev_unix | |||
prow = prow.drop(['start_hour', 'start_minute', 'offset'], axis=1) | |||
cycle = prow.iloc[0].cycle | |||
self.rhist = pd.concat([self.rhist, prow]) | |||
self.rhist = self.rhist.sort_values(by='start_unix').reset_index(drop=True) | |||
n += 1 | |||
# 2-1-2. 이상치 처리 : 비율에 따라 해당 행을 "삭제"(R_n <= 0.5) 또는 "조정"(R_n > 0.5)한다 | |||
R_n = (curr_unix - prev_unix) / cycle # R_n : 비율 | |||
# R_n이 0.5보다 작거나 같으면 해당 행을 삭제 | |||
if R_n <= 0.5: | |||
self.rhist = self.rhist.drop(index=n).reset_index(drop=True) | |||
if n >= self.rhist.index[-1]: | |||
break | |||
# 행삭제에 따른 curr_unix, R_n 재정의 | |||
curr_unix = self.rhist.loc[n, 'start_unix'] | |||
R_n = (curr_unix - prev_unix) / cycle # R_n : 비율 | |||
# R_n이 0.5보다 크면 해당 행 조정 (비율을 유지한 채로 현시시간 대체) | |||
if R_n > 0.5: | |||
# 신호 계획(prow) 불러오기 | |||
start_seconds = np.array(self.timetable.start_seconds) | |||
idx = (start_seconds <= curr_unix).sum() - 1 | |||
start_hour = self.timetable.iloc[idx].start_hour | |||
start_minute = self.timetable.iloc[idx].start_minute | |||
prow = self.plan[(self.plan.inter_no==inter_no) & (self.plan.start_hour==start_hour) & (self.plan.start_minute==start_minute)] # planned row | |||
# 조정된 현시시간 (prow에 R_n을 곱하고 정수로 바꿈) | |||
adjusted_dur = prow.copy()[[f'dura_{alph}{j}' for alph in ['A', 'B'] for j in range(1,9)]] * R_n | |||
int_parts = adjusted_dur.iloc[0].apply(lambda x: int(x)) | |||
frac_parts = adjusted_dur.iloc[0] - int_parts | |||
difference = round(adjusted_dur.iloc[0].sum()) - int_parts.sum() | |||
for _ in range(difference): # 소수 부분이 가장 큰 상위 'difference'개의 값에 대해 올림 처리 | |||
max_frac_index = frac_parts.idxmax() | |||
int_parts[max_frac_index] += 1 | |||
frac_parts[max_frac_index] = 0 # 이미 처리된 항목은 0으로 설정 | |||
# rhist에 조정된 현시시간을 반영 | |||
self.rhist.loc[n, [f'dura_{alph}{j}' for alph in ['A', 'B'] for j in range(1,9)]] = int_parts.values | |||
self.rhist.loc[n, 'cycle'] = int_parts.sum().sum() // 2 | |||
if n >= self.rhist.index[-1]: | |||
break | |||
prev_unix = curr_unix | |||
curr_unix = self.rhist.loc[n+1, 'start_unix'] | |||
# D_n 및 S_n 값 재정의 | |||
# 이 함수의 검증시 필요하나 전체 구동에는 필요없으므로 comment해놓음 | |||
# for n in range(len(self.rhist)): | |||
# curr_unix = self.rhist.iloc[n].start_unix # current start_unix | |||
# self.rhist.loc[n, ['D_n', 'S_n']] = self.calculate_DS(self.rhist, curr_unix) | |||
self.rhists.append(self.rhist) | |||
self.rhists = pd.concat(self.rhists)#.sort_values(by=['start_unix','inter_no']) | |||
self.rhists = self.rhists[self.rhists.start_unix >= self.present_time - self.subtractor // 2] | |||
def calculate_DS(self, rhist, curr_unix): | |||
# program_starts = np.array(self.timetable.start_seconds) | |||
# idx = (program_starts <= self.present_time).sum() - 1 | |||
# program_start = program_starts[idx] | |||
# if list(self.hours[self.hours <= curr_unix]): | |||
# ghour_lt_curr_unix = self.hours[self.hours <= curr_unix].max() # the greatest hour less than or equal to curr_unix | |||
# else: | |||
# ghour_lt_curr_unix = program_start | |||
# start_unixes = rhist.start_unix.unique() | |||
# start_unixes_lt_ghour = np.sort(start_unixes[start_unixes < ghour_lt_curr_unix]) # start unixes less than ghour_lt_curr_unix | |||
# # 기준유닉스(base_unix) : curr_unix보다 작은 hour 중에서 가장 큰 값으로부터 다섯 번째로 작은 start_unix | |||
# if len(start_unixes_lt_ghour) > 5: | |||
# base_unix = start_unixes_lt_ghour[-5] | |||
# # start_unixes_lt_ghour의 길이가 5 미만일 경우에는 맨 앞 start_unix로 base_unix를 지정 | |||
# else: | |||
# base_unix = rhist.start_unix.min() | |||
base_unix = curr_unix - self.subtractor // 2 | |||
abs_diff = (self.rhist['start_unix'] - base_unix).abs() | |||
closest_index = abs_diff.idxmin() | |||
base_unix = self.rhist.loc[closest_index, 'start_unix'] | |||
D_n = curr_unix - base_unix | |||
S_n_durs = rhist[(rhist.start_unix > base_unix) & (rhist.start_unix <= curr_unix)] \ | |||
[[f'dura_{alph}{j}' for alph in ['A', 'B'] for j in range(1,9)]] | |||
S_n = S_n_durs.values.sum() // 2 | |||
return D_n, S_n | |||
# 2-3. hrhists | |||
def make_hrhists(self): | |||
# 계층화된 형태로 변환 | |||
self.hrhists = [] # hierarchied recent history | |||
for row in self.rhists.itertuples(index=True): | |||
inter_no = row.inter_no | |||
start_unix = row.start_unix | |||
ind = (self.timetable['start_seconds'] <= row.start_unix).sum() - 1 | |||
start_hour = self.timetable.iloc[ind].start_hour | |||
start_minute = self.timetable.iloc[ind].start_minute | |||
self.isplit = self.isplits[(inter_no, start_hour, start_minute)] | |||
phas_As = [self.isplit[j][0] for j in self.isplit.keys()] | |||
phas_Bs = [self.isplit[j][1] for j in self.isplit.keys()] | |||
# durs_A = row[[f'dura_A{j}' for j in range(1,9)]] | |||
# durs_B = row[[f'dura_B{j}' for j in range(1,9)]] | |||
durs_A = [getattr(row, f'dura_A{j}') for j in range(1, 9)] | |||
durs_B = [getattr(row, f'dura_B{j}') for j in range(1, 9)] | |||
durations = [] | |||
for j in range(1, len(self.isplit)+1): | |||
ja = self.isplit[j][0] | |||
jb = self.isplit[j][1] | |||
if ja == jb: | |||
durations.append(min(durs_A[ja-1], durs_B[jb-1])) | |||
else: | |||
durations.append(abs(durs_A[ja-1] - durs_B[ja-1])) | |||
new_rows = pd.DataFrame({'inter_no':[inter_no] * len(durations), 'start_unix':[start_unix] * len(durations), | |||
'phas_A':phas_As, 'phas_B':phas_Bs, 'duration':durations}) | |||
self.hrhists.append(new_rows) | |||
self.hrhists = pd.concat(self.hrhists) | |||
# self.hrhists = self.hrhists.sort_values(by = ['start_unix', 'inter_no', 'phas_A', 'phas_B']).reset_index(drop=True) | |||
# 3. 이동류정보 전처리 | |||
def process_movement(self): | |||
print("3. 이동류정보 테이블을 변환합니다.") | |||
self.make_movement() | |||
self.time31 = datetime.now() | |||
self.update_movement() | |||
self.time32 = datetime.now() | |||
# 3-1. movement | |||
def make_movement(self): | |||
# - 아래 절차를 5초마다 반복 | |||
for fsec in range(self.present_time - 300, self.present_time + 1, 5): # fsec : unix time by Five SECond | |||
# 1. 상태 테이블 조회해서 전체 데이터중 필요데이터(교차로번호, A링 현시번호, A링 이동류번호, B링 현시번호, B링 이동류번호)만 수집 : A | |||
move = pd.read_csv(os.path.join(self.path_tables, 'move', f'move_{fsec}.csv'), index_col=0) | |||
# 2. 이력 테이블 조회해서 교차로별로 유닉스시간 최대인 데이터(교차로번호, 종료유닉스타임)만 수집 : B | |||
recent_histories = [group.iloc[-1:] for _, group in self.history[self.history['end_unix'] < fsec].groupby('inter_no')] # 교차로별로 유닉스시간이 최대인 행들 | |||
# print([group for _, group in self.history[self.history['end_unix'] < fsec].groupby('inter_no')]) | |||
if not recent_histories: | |||
rhistory = pd.DataFrame({'inter_no':[], 'end_unix':[]}) # recent history | |||
else: | |||
rhistory = pd.concat(recent_histories) | |||
recent_unix = rhistory[['inter_no', 'end_unix']] | |||
# 3. 상태 테이블 조회정보(A)와 이력 테이블 조회정보(B) 조인(키값 : 교차로번호) : C | |||
move = pd.merge(move, recent_unix, how='left', on='inter_no') | |||
move['end_unix'] = move['end_unix'].fillna(0).astype(int) | |||
# 4. C데이터 프레임에 신규 컬럼(시작 유닉스타임) 생성 후 종료유닉스 타임 값 입력, 종료 유닉스 타임 컬럼 제거 | |||
move = move.rename(columns = {'end_unix':'start_unix'}) | |||
# 5. 이동류 이력정보 READ | |||
# - CSV 파일로 서버에 저장된 이동류정보를 읽어옴(파일이 없는 경우에는 데이터가 없는 프레임 D 생성) | |||
try: | |||
if isinstance(movement, pd.DataFrame): # movement가 존재할 경우 그걸 그대로 씀. | |||
pass | |||
else: | |||
movement = pd.DataFrame() | |||
except NameError: # movement가 존재하지 않는 경우 생성 | |||
movement = pd.DataFrame() | |||
# 6. 이동류 이력정보 데이터테이블(D)에 C데이터 add | |||
movement = pd.concat([movement, move]) | |||
# 7. D데이터 프레임에서 중복데이터 제거(교차로번호, 시작 유닉스타임, A링 현시번호, B링 현시번호 같은 행은 제거) | |||
movement = movement.drop_duplicates(['inter_no','phas_A','phas_B','start_unix']) | |||
# 8. D데이터 보관 시간 기준시간을 시작 유닉스 타임의 최대값 - self.subtractor // 2을 값으로 산출하고, 보관 시간 기준시간보다 작은 시작 유닉스 타임을 가진 행은 모두 제거(1시간 데이터만 보관) | |||
movement = movement[movement.start_unix > fsec - self.subtractor // 2] | |||
# movement = movement.sort_values(by=['start_unix','inter_no','phas_A','phas_B']).reset_index(drop=True) | |||
self.movement = pd.read_csv(os.path.join(self.path_intermediates, 'movement', f'movement_{self.present_time}.csv'), index_col=0) | |||
# 3-2. movement_updated | |||
def update_movement(self): | |||
# 중복을 제거하고 (inter_no, start_unix) 쌍을 만듭니다. | |||
hrhists_inter_unix = set(self.hrhists[['inter_no', 'start_unix']].drop_duplicates().itertuples(index=False, name=None)) | |||
movement_inter_unix = set(self.movement[['inter_no', 'start_unix']].drop_duplicates().itertuples(index=False, name=None)) | |||
# hrhists에는 있지만 movement에는 없는 (inter_no, start_unix) 쌍을 찾습니다. | |||
missing_in_movement = hrhists_inter_unix - movement_inter_unix | |||
# 새로운 행들을 생성합니다. | |||
new_rows = [] | |||
if missing_in_movement: | |||
for inter_no, start_unix in missing_in_movement: | |||
# match1에서 해당 inter_no의 데이터를 찾습니다. | |||
new_row = self.match1[self.match1['inter_no'] == inter_no].copy() | |||
# start_unix 값을 설정합니다. | |||
new_row['start_unix'] = start_unix | |||
new_rows.append(new_row) | |||
# 새로운 데이터프레임을 생성하고 기존 movement 데이터프레임과 합칩니다. | |||
new_movement = pd.concat(new_rows, ignore_index=True) | |||
self.movement_updated = pd.concat([self.movement, new_movement], ignore_index=True) | |||
else: | |||
self.movement_updated = self.movement | |||
# 4. 통합테이블 생성 | |||
def make_histids(self): | |||
print("4. 통합 테이블을 생성합니다.") | |||
self.merge_dfs() | |||
self.time41 = datetime.now() | |||
# self.attach_children() | |||
# self.time42 = datetime.now() | |||
# 4-1. histid | |||
def merge_dfs(self): | |||
# movements and durations | |||
movedur = pd.merge(self.hrhists, self.movement_updated, how='inner', on=['inter_no', 'start_unix', 'phas_A', 'phas_B']) | |||
# movedur = movedur.sort_values(by=['start_unix', 'inter_no', 'phas_A','phas_B']) | |||
movedur = movedur[['inter_no', 'start_unix', 'phas_A', 'phas_B', 'move_A', 'move_B', 'duration']] | |||
# matching DataFrame에 대해 multi-index 설정 | |||
self.matching.set_index(['inter_no', 'move_no'], inplace=True) | |||
self.matching.sort_index(inplace=True) | |||
for row in movedur.itertuples(index=True): | |||
inter_no = row.inter_no | |||
start_unix = row.start_unix | |||
move_A = row.move_A | |||
move_B = row.move_B | |||
# incoming and outgoing edges A | |||
if move_A in [17, 18]: | |||
inc_edge_A = np.nan | |||
out_edge_A = np.nan | |||
else: | |||
match_A = self.matching.loc[(inter_no, move_A)] | |||
inc_edge_A = match_A.inc_edge.values[0] | |||
out_edge_A = match_A.out_edge.values[0] | |||
movedur.at[row.Index, 'inc_edge_A'] = inc_edge_A | |||
movedur.at[row.Index, 'out_edge_A'] = out_edge_A | |||
# incoming and outgoing edges B | |||
if move_B in [17, 18]: | |||
inc_edge_B = np.nan | |||
out_edge_B = np.nan | |||
else: | |||
match_B = self.matching.loc[(inter_no, move_B)] | |||
inc_edge_B = match_B.inc_edge.values[0] | |||
out_edge_B = match_B.out_edge.values[0] | |||
movedur.at[row.Index, 'inc_edge_B'] = inc_edge_B | |||
movedur.at[row.Index, 'out_edge_B'] = out_edge_B | |||
# 이동류 컬럼 제거 | |||
movedur = movedur.drop(['move_A', 'move_B'], axis=1) | |||
self.histid = movedur.copy() # history with edge ids (incoming and outgoing edge ids) | |||
self.histid['node_id'] = self.histid['inter_no'].map(self.inter2node) | |||
self.histid = self.histid[['inter_no', 'node_id', 'start_unix', 'phas_A', 'phas_B', 'duration', 'inc_edge_A', 'out_edge_A', 'inc_edge_B', 'out_edge_B']] | |||
histid_start = self.present_time - 600 | |||
self.histid = self.histid[self.histid.start_unix > histid_start] | |||
# 4-2. histids | |||
def attach_children(self): | |||
''' | |||
자식교차로에 대한 진입·진출 엣지 정보를 붙여주는 함수 | |||
input : | |||
(1) histid | |||
- 각 교차로에 대한 (시작유닉스, A현시, B현시)별 현시시간, 진입·진출엣지 | |||
- 부모교차로(주교차로)에 대해서만 값이 지정되어 있음 | |||
(2) match6 | |||
- (현시, 링)별 진입·진출엣지 | |||
- 자식교차로(유턴 및 연동교차로)에 대해서도 값이 지정되어 있음 | |||
(3) parent_ids : 부모교차로 목록 | |||
(4) pa2ch : 각 부모교차로id를 부모교차로가 포함하고 있는 자식교차로들의 id들의 리스트로 대응시키는 딕셔너리 | |||
output : histids | |||
- 모든(부모 및 자식) 교차로에 대한 시작유닉스 (시작유닉스, A현시, B현시)별 현시시간, 진입·진출엣지 | |||
''' | |||
new_histids = [] | |||
for parent_id in self.parent_ids: | |||
for child_id in self.pa2ch[parent_id]: | |||
new_histid = self.histid.copy()[self.histid.node_id==parent_id] | |||
new_histid[['inc_edge_A', 'out_edge_A', 'inc_edge_B', 'out_edge_B']] = np.nan | |||
for i, row in new_histid.iterrows(): | |||
phas_A = row.phas_A | |||
phas_B = row.phas_B | |||
new_match = self.match6[self.match6.node_id==child_id] | |||
Arow = new_match[(new_match.phase_no==phas_A) & (new_match.ring_type=='A')] | |||
if ~ Arow[['inc_edge', 'out_edge']].isna().all().all(): | |||
inc_edge = Arow.iloc[0].inc_edge | |||
out_edge = Arow.iloc[0].out_edge | |||
new_histid.loc[i, ['inc_edge_A', 'out_edge_A']] = [inc_edge, out_edge] | |||
Brow = new_match[(new_match.phase_no==phas_B) & (new_match.ring_type=='B')] | |||
if ~ Brow[['inc_edge', 'out_edge']].isna().all().all(): | |||
inc_edge = Brow.iloc[0].inc_edge | |||
out_edge = Brow.iloc[0].out_edge | |||
new_histid.loc[i, ['inc_edge_B', 'out_edge_B']] = [inc_edge, out_edge] | |||
new_histid.loc[i, 'node_id'] = child_id | |||
new_histids.append(new_histid) | |||
new_histids = pd.concat(new_histids) | |||
self.histids = pd.concat([self.histid.copy(), new_histids]) | |||
self.histids = self.histids.sort_values(by=['start_unix', 'node_id', 'phas_A', 'phas_B']).reset_index(drop=True) | |||
# 5. 신호 생성 | |||
def get_signals(self): | |||
print("5. 신호를 생성합니다.") | |||
self.initialize_states() | |||
self.time51 = datetime.now() | |||
self.assign_signals() | |||
self.time52 = datetime.now() | |||
self.set_timepoints() | |||
self.time53 = datetime.now() | |||
self.assign_red_yellow() | |||
self.time54 = datetime.now() | |||
self.make_tl_file() | |||
self.time55 = datetime.now() | |||
# 5-1. 신호초기화 | |||
def initialize_states(self): | |||
''' | |||
신호 초기화 | |||
input : | |||
(1) net : 네트워크 | |||
(2) nodes : 노드 목록 | |||
(3) histids : 모든 교차로에 대한 시작유닉스 (시작유닉스, A현시, B현시)별 현시시간, 진입·진출엣지 | |||
output : node2init | |||
- 각 노드를 초기화된 신호로 맵핑하는 딕셔너리 | |||
- 초기화된 신호란, 우회전을 g로 나머지는 r로 지정한 신호를 말함. | |||
''' | |||
self.node2init = {} | |||
for node in self.nodes: | |||
node_id = node.getID() | |||
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 = [] | |||
for i, ci in conns: | |||
if ci.getTLLinkIndex() < 0: | |||
continue | |||
are_foes = False | |||
for j, cj in conns: | |||
if ci.getTo() == cj.getTo(): | |||
continue | |||
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.histids.iterrows(): | |||
node_id = row['node_id'] | |||
inc_edge_A = row.inc_edge_A | |||
inc_edge_B = row.inc_edge_B | |||
out_edge_A = row.out_edge_A | |||
out_edge_B = row.out_edge_B | |||
if pd.isna(inc_edge_A) or pd.isna(out_edge_A): | |||
pass | |||
else: | |||
inc_edge_A = self.net.getEdge(inc_edge_A) | |||
out_edge_A = self.net.getEdge(out_edge_A) | |||
for conn in inc_edge_A.getConnections(out_edge_A): | |||
index = conn.getTLLinkIndex() | |||
if index >= 0: | |||
self.node2init[node_id][index] = 'r' | |||
if pd.isna(inc_edge_B) or pd.isna(out_edge_B): | |||
pass | |||
else: | |||
inc_edge_B = self.net.getEdge(inc_edge_B) | |||
out_edge_B = self.net.getEdge(out_edge_B) | |||
for conn in inc_edge_B.getConnections(out_edge_B): | |||
index = conn.getTLLinkIndex() | |||
if index >= 0: | |||
self.node2init[node_id][index] = 'r' | |||
# 5-2. 녹색신호 부여 | |||
def assign_signals(self): | |||
''' | |||
진입·진출엣지를 신호문자열로 배정 | |||
input : | |||
(1) histids : 모든 교차로에 대한 (시작유닉스, A현시, B현시)별 현시시간, 진입·진출엣지 | |||
(2) node2init : 각 노드를 초기화된 신호로 맵핑하는 딕셔너리 | |||
(3) net : 네트워크 | |||
output : sigtable | |||
- 모든 교차로에 대한 (시작유닉스, A현시, B현시)별 현시시간, 신호문자열 | |||
- 황색 및 적색신호는 아직 반영되지 않았음. | |||
''' | |||
self.sigtable = self.histids.copy() | |||
self.sigtable['init_state'] = self.sigtable['node_id'].map(self.node2init) | |||
self.sigtable['state'] = self.sigtable['init_state'].map(lambda x:''.join(x)) | |||
for row in self.sigtable.itertuples(index=True): | |||
node_id = row.node_id | |||
inc_edge_A = row.inc_edge_A | |||
inc_edge_B = row.inc_edge_B | |||
out_edge_A = row.out_edge_A | |||
out_edge_B = row.out_edge_B | |||
state = copy.deepcopy(self.node2init)[node_id] | |||
if pd.isna(inc_edge_A) or pd.isna(out_edge_A): | |||
pass | |||
else: | |||
inc_edge_A = self.net.getEdge(inc_edge_A) | |||
out_edge_A = self.net.getEdge(out_edge_A) | |||
for conn in inc_edge_A.getConnections(out_edge_A): | |||
index = conn.getTLLinkIndex() | |||
if index >= 0: | |||
state[index] = 'G' | |||
self.sigtable.at[row.Index, 'state'] = ''.join(state) | |||
if pd.isna(inc_edge_B) or pd.isna(out_edge_B): | |||
pass | |||
else: | |||
inc_edge_B = self.net.getEdge(inc_edge_B) | |||
out_edge_B = self.net.getEdge(out_edge_B) | |||
for conn in inc_edge_B.getConnections(out_edge_B): | |||
index = conn.getTLLinkIndex() | |||
if index >= 0: | |||
state[index] = 'G' | |||
self.sigtable.at[row.Index, 'state'] = ''.join(state) | |||
self.sigtable = self.sigtable.dropna(subset='state') | |||
self.sigtable = self.sigtable.reset_index(drop=True) | |||
self.sigtable['phase_sumo'] = self.sigtable.groupby(['node_id', 'start_unix']).cumcount() | |||
self.sigtable = self.sigtable[['node_id', 'start_unix', 'phase_sumo', 'duration', 'state']] | |||
# self.sigtable = self.sigtable.sort_values(by=['start_unix', 'node_id']) | |||
self.sigtable['start_dt'] = self.sigtable['start_unix'].apply(lambda x:datetime.fromtimestamp(x)) | |||
# 5-3. 신호 파일의 시작 및 종료시각 설정 | |||
def set_timepoints(self): | |||
self.offsets = {} | |||
self.Sigtable = [] | |||
sim_start = self.present_time - 300 | |||
for node_id, group in self.sigtable.groupby('node_id'): | |||
lsbs = group[group['start_unix'] < sim_start]['start_unix'].max() # the last start_unix before sim_start | |||
self.offsets[node_id] = lsbs - sim_start | |||
group = group[group.start_unix >= lsbs] | |||
start_unixes = np.array(group.start_unix) | |||
start_unixes = np.sort(np.unique(start_unixes))[:self.node2num_cycles[node_id]] | |||
group = group[group.start_unix.isin(start_unixes)] | |||
self.Sigtable.append(group) | |||
self.Sigtable = pd.concat(self.Sigtable) | |||
# 5-4. 적색 및 황색신호 부여 | |||
def assign_red_yellow(self): | |||
''' | |||
적색, 황색신호를 반영한 신호문자열 배정 | |||
input : Sigtable | |||
- 모든 교차로에 대한 (시작유닉스, 세부현시번호)별 현시시간, 신호문자열, 진입·진출엣지 | |||
* 세부현시란 오버랩을 반영한 현시번호를 뜻함. | |||
output : SIGTABLE | |||
- 모든 교차로에 대한 (시작유닉스, 녹황적세부현시번호)별 현시시간, (황·적색신호가 포함된) 신호문자열 | |||
* 녹황적세부현시번호란 세부현시번호에 r, g, y 옵션까지 포함된 현시번호를 뜻함. | |||
''' | |||
self.SIGTABLE = [] | |||
for _, group in self.Sigtable.groupby('node_id'): | |||
new_rows_list = [] | |||
for i in range(1, len(group)): | |||
prev_row = group.iloc[i-1:i].copy() | |||
next_row = group.iloc[i:i+1].copy() | |||
new_rows = pd.concat([prev_row, prev_row, next_row]).reset_index(drop=True) | |||
new_rows.loc[0, 'phase_sumo'] = str(prev_row.phase_sumo.iloc[0]) + '_g' | |||
new_rows.loc[0, 'duration'] = new_rows.loc[0, 'duration'] - 5 | |||
new_rows.loc[1, 'phase_sumo'] = str(prev_row.phase_sumo.iloc[0]) + '_y' | |||
new_rows.loc[1, 'duration'] = 4 | |||
yellow_state = '' | |||
red_state = '' | |||
for a, b in zip(prev_row.state.iloc[0], next_row.state.iloc[0]): | |||
if a == 'G' and b == 'r': | |||
yellow_state += 'y' | |||
red_state += 'r' | |||
else: | |||
yellow_state += a | |||
red_state += a | |||
new_rows.loc[2, 'phase_sumo'] = str(next_row.phase_sumo.iloc[0]) + '__r' | |||
new_rows.loc[2, 'duration'] = 1 | |||
new_rows.loc[1, 'state'] = yellow_state | |||
new_rows.loc[2, 'state'] = red_state | |||
new_rows_list.append(new_rows) | |||
next_row['phase_sumo'] = str(next_row.phase_sumo.iloc[0]) + '_g' | |||
next_row['duration'] -= 5 | |||
new_rows_list.append(next_row) | |||
new_rows = pd.concat(new_rows_list) | |||
self.SIGTABLE.append(new_rows) | |||
self.SIGTABLE = pd.concat(self.SIGTABLE).sort_values(by=['node_id', 'start_unix', 'phase_sumo']) | |||
# 5-5. 신호파일 생성 | |||
def make_tl_file(self): | |||
strings = ['<additional>\n'] | |||
for node_id, group in self.SIGTABLE.groupby('node_id'): | |||
strings.append(f' <tlLogic id="{node_id}" type="static" programID="{node_id}_prog" offset="{self.offsets[node_id]}">\n') | |||
for row in group.itertuples(index=True): | |||
duration = row.duration | |||
state = row.state | |||
strings.append(f' <phase duration="{duration}" state="{state}"/>\n') | |||
strings.append(' </tlLogic>\n') | |||
strings.append('</additional>') | |||
strings = ''.join(strings) | |||
# 저장 | |||
self.path_output = os.path.join(self.path_results, f'sn_{self.present_time}.add.xml') | |||
with open(self.path_output, 'w') as f: | |||
f.write(strings) | |||
# 6. 이슈사항 저장 | |||
def write_issues(self): | |||
print('6. 이슈사항을 저장합니다.') | |||
path_issues = os.path.join(self.path_results, "issues_generate_signals.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): | |||
self.time0 = datetime.now() | |||
# 1. 데이터 준비 | |||
self.prepare_data() | |||
self.time1 = datetime.now() | |||
# 2. 신호이력 전처리 | |||
self.process_history() | |||
self.time2 = datetime.now() | |||
# 3. 이동류정보 전처리 | |||
self.process_movement() | |||
self.time3 = datetime.now() | |||
# 4. 통합테이블 생성 | |||
self.make_histids() | |||
self.time4 = datetime.now() | |||
# # 5. 신호 생성 | |||
# self.get_signals() | |||
# self.time5 = datetime.now() | |||
# # 6. 이슈사항 저장 | |||
# self.write_issues() | |||
# self.time6 = datetime.now() | |||
print('(1)', self.time1 - self.time0) | |||
print('(1-1)', self.time11 - self.time0) | |||
print('(1-2)', self.time12 - self.time11) | |||
print('(1-3)', self.time13 - self.time12) | |||
print('(1-4)', self.time14 - self.time13) | |||
print('(1-5)', self.time15 - self.time14) | |||
print('(2)', self.time2 - self.time1) | |||
print('(2-1)', self.time21 - self.time1) | |||
print('(2-2)', self.time22 - self.time21) | |||
print('(2-3)', self.time23 - self.time22) | |||
print('(3)', self.time3 - self.time2) | |||
print('(3-1)', self.time31 - self.time2) | |||
print('(3-2)', self.time32 - self.time31) | |||
print('(4)', self.time4 - self.time3) | |||
print('(4-1)', self.time41 - self.time3) | |||
# print('(4-2)', self.time42 - self.time41) | |||
# print('(5)', self.time5 - self.time4) | |||
# print('(5-1)', self.time51 - self.time4) | |||
# print('(5-2)', self.time52 - self.time51) | |||
# print('(5-3)', self.time53 - self.time52) | |||
# print('(5-4)', self.time54 - self.time53) | |||
# print('(5-5)', self.time55 - self.time54) | |||
# print('(6)', self.time6 - self.time5) | |||
print('total time :', self.time41 - self.time0) | |||
if __name__ == '__main__': | |||
self = SignalGenerator() | |||
self.main() | |||
# self.path_unit = os.path.join(self.path_root, 'Analysis', '0207_unit_test') | |||
# self.hrhists.to_csv(os.path.join(self.path_unit, 'hrhists.csv')) | |||
# self.histids.to_csv(os.path.join(self.path_unit, 'histids.csv')) | |||
# self.sigtable.to_csv(os.path.join(self.path_unit, 'sigtable.csv')) | |||
# self.Sigtable.to_csv(os.path.join(self.path_unit, 'ssigtable.csv')) | |||
# print("elapsed time :", datetime.now() - starting_time) |