| @ -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) | |||