@ -1,3 +1,4 @@ | |||||
Data/tables/move/* | Data/tables/move/* | ||||
Intermediates/histid/* | 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) |