{ "cells": [ { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "import os, sys, copy, sumolib, json, datetime\n", "import pandas as pd\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "class DailyPreprocessor():\n", " def __init__(self):\n", " pass\n", "self = DailyPreprocessor()" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "c:\\Github\\snits_siggen\n", "c:\\Github\\snits_siggen\\Data\n", "c:\\Github\\snits_siggen\\Intermediates\n", "c:\\Github\\snits_siggen\\Results\n", "c:\\Github\\snits_siggen\\Data\\tables\n", "c:\\Github\\snits_siggen\\Data\\networks\n", "c:\\Github\\snits_siggen\\Scripts\n" ] } ], "source": [ "# 루트폴더 지정\n", "self.path_root = os.path.dirname(os.path.dirname(os.path.join(os.path.abspath('.'))))\n", "print(self.path_root)\n", "\n", "with open(os.path.join(self.path_root, 'configs', 'config.json'), 'r') as file:\n", " config = json.load(file)\n", "\n", "# 주요 폴더 경로 지정\n", "self.paths = config['paths']\n", "self.path_data = os.path.join(self.path_root, *self.paths['data'])\n", "self.path_intermediates = os.path.join(self.path_root, *self.paths['intermediates'])\n", "self.path_results = os.path.join(self.path_root, *self.paths['results'])\n", "self.path_tables = os.path.join(self.path_root, *self.paths['tables'])\n", "self.path_networks = os.path.join(self.path_root, *self.paths['networks'])\n", "self.path_scripts = os.path.join(self.path_root, *self.paths['scripts'])\n", "print(self.path_data)\n", "print(self.path_intermediates)\n", "print(self.path_results)\n", "print(self.path_tables)\n", "print(self.path_networks)\n", "print(self.path_scripts)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "# 1-1. 네트워크 불러오기\n", "self.net = sumolib.net.readNet(os.path.join(self.path_networks, 'sn.net.xml'))" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "# 1-2. 테이블 불러오기\n", "self.alphs = ['A', 'B']\n", "\n", "loading_dtype_1 = {'CRSRD_ID':int, 'CRSRD_NM':str, 'CRSRD_TYPE':int, \n", " 'CTRLER_TYPE':str, 'CYCL':int, 'DAY':int, \n", " 'FLOW_NO':int, 'FRI_PLAN_NO':int, 'FRST_REG_DT':str, \n", " 'GRP_NO':int, 'HOUR':int, 'LAST_MDFCN_DT':str, \n", " 'LGTD':float, 'LOS_YN':int, 'LTTD':float, \n", " 'MAIN_CRSRD_ID':str, 'MAIN_PHASE':int, 'MIN':int, \n", " 'MNTH':int, 'MON_PLAN_NO':int, 'NODE_ID':str, \n", " 'OFFSET':int, 'PHASE':int, 'PHASE_DT':str, \n", " 'PLAN_NO':int, 'PPC_TYPE':int, 'RING':str, \n", " 'RINGA_FLOW':int, 'RINGA_MIN_SEC':int, 'RINGA_RED_SEC':int, \n", " 'RINGA_YELLO_SEC':int, 'RINGB_FLOW':int, 'RINGB_MIN_SEC':int, \n", " 'RINGB_RED_SEC':int, 'RINGB_YELLO_SEC':int, 'SAT_PLAN_NO':int, \n", " 'SIGL_ANGLE':str, 'STOS_NO':int, 'SUN_PLAN_NO':int, \n", " 'THU_PLAN_NO':int, 'TRFLIG_TYPE':int, 'TUE_PLAN_NO':int, \n", " 'USE_YN':int, 'WED_PLAN_NO':int, 'adj_inc_edge_id':str, \n", " 'adj_out_edge_id':str, 'child_id':str, 'condition':str, \n", " 'inc_edge_id':str, 'inter_no':int, 'inter_type':int, \n", " 'node_id':str, 'out_edge_id':str, 'parent_id':str, \n", " 'phase_no':int, 'ring_type':str, 'turn_type':str}\n", "loading_dtype_2 = {f'RING{alph}_PHASE{i}':int for alph in self.alphs for i in range(1,9)}\n", "loading_dtype = {**loading_dtype_1, **loading_dtype_2}\n", "\n", "# 테이블 불러오기\n", "\n", "# 수작업으로 만든 테이블\n", "self.inter_node = pd.read_csv(os.path.join(self.path_tables, 'inter_node.csv'), dtype=loading_dtype)\n", "self.turn_type = pd.read_csv(os.path.join(self.path_tables, 'turn_type.csv'), dtype=loading_dtype)\n", "self.uturn = pd.read_csv(os.path.join(self.path_tables, 'uturn.csv'), dtype=loading_dtype)\n", "self.u_condition= pd.read_csv(os.path.join(self.path_tables, 'u_condition.csv'), dtype=loading_dtype)\n", "self.coord = pd.read_csv(os.path.join(self.path_tables, 'coord.csv'), dtype=loading_dtype)\n", "self.turn_type = pd.read_csv(os.path.join(self.path_tables, 'turn_type.csv'), dtype=loading_dtype)\n", "\n", "# DB에서 fetch하는 테이블\n", "self.dayplan = pd.read_csv(os.path.join(self.path_tables, 'TC_IF_TOD_DAY_PLAN.csv'), dtype=loading_dtype)\n", "self.holyplan = pd.read_csv(os.path.join(self.path_tables, 'TC_IF_TOD_HOLIDAY_PLAN.csv'), dtype=loading_dtype)\n", "self.red_yel = pd.read_csv(os.path.join(self.path_tables, 'TC_IF_TOD_RED_YELLO.csv'), dtype=loading_dtype)\n", "self.weekplan = pd.read_csv(os.path.join(self.path_tables, 'TC_IF_TOD_WEEK_PLAN.csv'), dtype=loading_dtype)\n", "self.history = pd.read_csv(os.path.join(self.path_tables, 'TL_IF_SIGL_CYCL.csv'), dtype=loading_dtype)\n", "self.inter_info = pd.read_csv(os.path.join(self.path_tables, 'TM_FA_CRSRD.csv'), dtype=loading_dtype)\n", "self.angle = pd.read_csv(os.path.join(self.path_tables, 'TN_IF_SIGL_FLOW.csv'), dtype=loading_dtype)\n", "\n", "# 컬럼명 변경\n", "rename_cname_1 = {'CRSRD_ID':'inter_no', 'CRSRD_NM':'inter_name', 'CRSRD_TYPE':'inter_type', \n", " 'CYCL':'cycle', 'DAY':'DD', 'FLOW_NO':'move_no', \n", " 'GRP_NO':'group_no', 'HOUR':'hh', 'LGTD':'inter_lon', \n", " 'LTTD':'inter_lat', 'MAIN_CRSRD_ID':'parent_id','MAIN_PHASE':'main_phase', \n", " 'MIN':'mm', 'MNTH':'MM', 'NODE_ID':'node_id', \n", " 'OFFSET':'offset', 'PHASE':'phase_no', 'RING':'ring_type', \n", " 'RINGA_RED_SEC':'red_A', 'RINGA_YELLO_SEC':'yel_A', 'RINGB_RED_SEC':'red_B', \n", " 'RINGB_YELLO_SEC':'yel_B', 'SIGL_ANGLE':'angle_code', 'PLAN_NO':'plan_no'}\n", "\n", "rename_cname_2 = {f'RING{alph}_PHASE{i}':f'dura_{alph}{i}' for alph in self.alphs for i in range(1,9)}\n", "rename_cname = {**rename_cname_1, **rename_cname_2}\n", "\n", "self.dayplan = self.dayplan.rename(columns=rename_cname)\n", "self.holyplan = self.holyplan.rename(columns=rename_cname)\n", "self.red_yel = self.red_yel.rename(columns=rename_cname)\n", "self.weekplan = self.weekplan.rename(columns=rename_cname)\n", "self.history = self.history.rename(columns=rename_cname)\n", "self.inter_info = self.inter_info.rename(columns=rename_cname)\n", "self.angle = self.angle.rename(columns=rename_cname)\n", "\n", "# 교차로목록, 노드목록 정의\n", "self.inter_nos = [int(x) for x in sorted(self.inter_info.inter_no.unique())]\n", "self.node_ids = sorted(self.inter_node.node_id.unique())" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "# 1-3. 네트워크 무결성 검사\n", "# (생략)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "# 1-4. 테이블 무결성 검사\n", "# (생략)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "# 1-5. 주요 객체 (리스트, 딕셔너리) 저장\n", "\n", "# 주교차로 / 부교차로 / 유턴교차로 / 연등교차로 노드id\n", "self.parent_ids = sorted(self.inter_node[self.inter_node.inter_type==0].node_id.unique())\n", "self.child_ids = sorted(self.inter_node[self.inter_node.inter_type==1].node_id.unique())\n", "self.uturn_ids = sorted(self.uturn.node_id.unique())\n", "self.coord_ids = sorted(self.coord.node_id.unique())" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "# 교차로번호 - 노드id 간 딕셔너리\n", "self.node2inter = dict(zip(self.inter_node['node_id'], self.inter_node['inter_no']))\n", "self.inter2nodes = {inter:[key for key, value in self.node2inter.items() if value == inter] for inter in self.node2inter.values()}\n", "self.inter2node = dict(zip(self.inter_node[self.inter_node.inter_type==0]['inter_no'], self.inter_node[self.inter_node.inter_type==0]['node_id']))\n", "self.node2type = dict(zip(self.inter_node['node_id'], self.inter_node['inter_type']))\n", "# 주교차로id - 부교차로id 간 딕셔너리\n", "self.child2parent = dict()\n", "for child_id in list(self.inter_node[self.inter_node.inter_type==1]['node_id'].unique()):\n", " inter_no = self.inter_node[self.inter_node.node_id==child_id].iloc[0].inter_no\n", " parent_id = self.inter_node[self.inter_node.inter_no==inter_no][self.inter_node[self.inter_node.inter_no==inter_no].inter_type==0].iloc[0].node_id\n", " self.child2parent[child_id] = parent_id\n", "self.parent2childs = {parent_id:[key for key, value in self.child2parent.items() if value == parent_id] for parent_id in self.child2parent.values()}" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
inter_noplan_nohhmmcycleoffsetdura_A1dura_A2dura_A3dura_A4...dura_A8dura_B1dura_B2dura_B3dura_B4dura_B5dura_B6dura_B7dura_B8LAST_MDFCN_DT
01751001605737395529...03739255900002024-07-12 16:36:43.702267
11751701704040425533...04042295900002024-07-12 16:36:43.702267
21751901802843455537...04345335900002024-07-12 16:36:43.702267
3175118301901846485541...04648375900002024-07-12 16:36:43.702267
41761001501313773400...0377340000002024-07-12 16:36:43.702267
\n", "

5 rows × 23 columns

\n", "
" ], "text/plain": [ " inter_no plan_no hh mm cycle offset dura_A1 dura_A2 dura_A3 \\\n", "0 175 1 0 0 160 57 37 39 55 \n", "1 175 1 7 0 170 40 40 42 55 \n", "2 175 1 9 0 180 28 43 45 55 \n", "3 175 1 18 30 190 18 46 48 55 \n", "4 176 1 0 0 150 131 37 73 40 \n", "\n", " dura_A4 ... dura_A8 dura_B1 dura_B2 dura_B3 dura_B4 dura_B5 \\\n", "0 29 ... 0 37 39 25 59 0 \n", "1 33 ... 0 40 42 29 59 0 \n", "2 37 ... 0 43 45 33 59 0 \n", "3 41 ... 0 46 48 37 59 0 \n", "4 0 ... 0 37 73 40 0 0 \n", "\n", " dura_B6 dura_B7 dura_B8 LAST_MDFCN_DT \n", "0 0 0 0 2024-07-12 16:36:43.702267 \n", "1 0 0 0 2024-07-12 16:36:43.702267 \n", "2 0 0 0 2024-07-12 16:36:43.702267 \n", "3 0 0 0 2024-07-12 16:36:43.702267 \n", "4 0 0 0 2024-07-12 16:36:43.702267 \n", "\n", "[5 rows x 23 columns]" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 1-6. 신호계획 테이블 통합\n", "\n", "now = datetime.datetime.now()\n", "# 월, 일\n", "MM, DD, hh, mm = now.month, now.day, now.hour, now.minute\n", "hplan = self.holyplan[(self.holyplan.MM==MM) & (self.holyplan.DD==DD)]\n", "\n", "# 요일\n", "dow_number = now.weekday()\n", "dows = [dow for dow in self.weekplan.columns if dow.endswith('PLAN_NO')]\n", "dows = dows[1:] + dows[0:1]\n", "dow = dows[dow_number]\n", "\n", "# 오늘에 해당하는 신호계획 추출\n", "if len(hplan):\n", " # (inter_no, plan_no) 목록 \n", " inter_pnos = list(zip(hplan['inter_no'], hplan['plan_no']))\n", "else:\n", " inter_pnos = list(zip(self.weekplan.inter_no, self.weekplan[dow]))\n", "\n", "# 통합된 테이블 (오늘)\n", "self.plan = self.dayplan.copy()\n", "self.plan['inter_pno'] = list(zip(self.plan['inter_no'], self.plan['plan_no']))\n", "self.plan = self.plan[(self.plan.inter_pno.isin(inter_pnos))]\n", "self.plan = self.plan.drop(columns='inter_pno')\n", "self.plan.head()" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "# angle 테이블에서 필요없는 현시번호 제거\n", "self.angle_new = []\n", "for inter_no in self.inter_nos:\n", " ang = self.angle[self.angle.inter_no==inter_no]\n", " max_phase_no = ang.dropna(subset='angle_code')['phase_no'].max()\n", " self.angle_new.append(ang[ang.phase_no <= max_phase_no])\n", "self.angle = pd.concat(self.angle_new)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "# 노드id 생성\n", "self.angle['node_id'] = self.angle['inter_no'].map(self.inter2node)\n", "# 주교차로id 생성\n", "self.angle['parent_id'] = self.angle['node_id']\n", "# 불필요컬럼 제거\n", "self.angle = self.angle.drop(columns='move_no')\n", "# 진입/진출 엣지id 지정\n", "for index, row in self.angle.iterrows():\n", " node_id = row.node_id\n", " if not isinstance(row.angle_code, str) or len(row.angle_code)!=6:\n", " continue\n", " # 방위각\n", " inc_angle = int(row.angle_code[:3])\n", " out_angle = int(row.angle_code[3:])\n", " self.angle.at[index, 'inc_angle'] = inc_angle\n", " self.angle.at[index, 'out_angle'] = out_angle\n", " # 일반각 및 라디안으로 변환, 실제 방향 설정\n", " inc_angle = (90 - inc_angle) % 360\n", " inc_angle = inc_angle * np.pi / 180\n", " inc_vec_true = np.array([np.cos(inc_angle), np.sin(inc_angle)])\n", " out_angle = (90 - out_angle) % 360\n", " out_angle = out_angle * np.pi / 180\n", " out_vec_true = np.array([np.cos(out_angle), np.sin(out_angle)])\n", "\n", " # 진입로 목록\n", " inc_edge_ids = [edge.getID() for edge in self.net.getNode(node_id).getIncoming()]\n", " inc_vecs = []\n", " for inc_edge_id in inc_edge_ids:\n", " init_pt = self.net.getEdge(inc_edge_id).getShape()[-1]\n", " term_pt = self.net.getEdge(inc_edge_id).getShape()[-2]\n", " assert init_pt != term_pt\n", " inc_vec = np.array(term_pt) - np.array(init_pt)\n", " inc_vec = inc_vec / np.linalg.norm(inc_vec)\n", " inc_vecs.append(inc_vec)\n", " # 각도에 맞는 진입로 지정\n", " max_index = np.argmax([np.dot(inc_vec, inc_vec_true) for inc_vec in inc_vecs])\n", " inc_edge_id = inc_edge_ids[max_index]\n", " self.angle.at[index, 'inc_edge_id'] = inc_edge_id\n", "\n", " # 진출로 목록\n", " out_edge_ids = [edge.getID() for edge in self.net.getNode(node_id).getOutgoing()]\n", " out_vecs = []\n", " for out_edge_id in out_edge_ids:\n", " init_pt = self.net.getEdge(out_edge_id).getShape()[0]\n", " term_pt = self.net.getEdge(out_edge_id).getShape()[1]\n", " assert init_pt != term_pt\n", " out_vec = np.array(term_pt) - np.array(init_pt)\n", " out_vec = out_vec / np.linalg.norm(out_vec)\n", " out_vecs.append(out_vec)\n", " # 각도에 맞는 진출로 지정\n", " max_index = np.argmax([np.dot(out_vec, out_vec_true) for out_vec in out_vecs])\n", " out_edge_id = out_edge_ids[max_index]\n", " self.angle.at[index, 'out_edge_id'] = out_edge_id\n", "# matcher로 테이블명 변경\n", "self.matcher = self.angle.drop(columns='angle_code')" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "# turn_type 지정\n", "io2turn_type = dict()\n", "for row in self.turn_type.itertuples():\n", " if not (row.inc_edge_id, row.out_edge_id) in io2turn_type:\n", " io2turn_type[(row.inc_edge_id, row.out_edge_id)] = row.turn_type\n", " else:\n", " if io2turn_type[(row.inc_edge_id, row.out_edge_id)] != row.turn_type:\n", " print('the dictionary is not well-defined')\n", "self.matcher['turn_type'] = self.matcher.apply(lambda row:io2turn_type.get((row['inc_edge_id'], row['out_edge_id']), None), axis=1)\n", "self.matcher['turn_type'] = self.matcher['turn_type'].map({'straight': 'S', 'left': 'L', 'right': 'R'})\n", "# node_type 지정\n", "self.matcher['node_type'] = 'normal'" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "# 유턴조건 관련 객체 정의\n", "self.angle_sep = 20\n", "u_condition = dict(zip(self.u_condition.child_id, self.u_condition.condition))\n", "\n", "# (노드id, 진입엣지) 별 각도\n", "NIA = [] # (node_id, inc_edge_id, angle)\n", "NIs = set(zip(self.matcher.node_id, self.matcher.inc_edge_id)) # (node_id, inc_edge_id)\n", "NIs = sorted(ni for ni in NIs if not pd.isna(ni[1]))\n", "for node_id, inc_edge_id in NIs:\n", " ang = self.matcher[(self.matcher.node_id==node_id) & (self.matcher.inc_edge_id==inc_edge_id)]\n", " mean_x = np.mean(np.cos(np.deg2rad(ang.inc_angle)))\n", " mean_y = np.mean(np.sin(np.deg2rad(ang.inc_angle)))\n", " mean_angle = int(np.rad2deg(np.arctan2(mean_y, mean_x)) % 360)\n", " NIA.append(pd.DataFrame({'node_id':[node_id], 'inc_edge_id':[inc_edge_id], 'angle':[mean_angle]}))\n", "NIA = pd.concat(NIA).reset_index(drop=True)\n", "\n", "# (노드id, 진출엣지) 별 각도\n", "NOA = [] # (node_id, out_edge_id, angle)\n", "NOs = set(zip(self.matcher.node_id, self.matcher.out_edge_id)) # (node_id, out_edge_id)\n", "NOs = sorted(no for no in NOs if not pd.isna(no[1]))\n", "for node_id, out_edge_id in NOs:\n", " ang = self.matcher[(self.matcher.node_id==node_id) & (self.matcher.out_edge_id==out_edge_id)]\n", " mean_x = np.mean(np.cos(np.deg2rad(ang.out_angle)))\n", " mean_y = np.mean(np.sin(np.deg2rad(ang.out_angle)))\n", " mean_angle = int(np.rad2deg(np.arctan2(mean_y, mean_x)) % 360)\n", " NOA.append(pd.DataFrame({'node_id':[node_id], 'out_edge_id':[out_edge_id], 'angle':[mean_angle]}))\n", "NOA = pd.concat(NOA).reset_index(drop=True)" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "uangles = []\n", "for uturn_id in self.uturn_ids:\n", " parent_id = self.child2parent[uturn_id] # 주교차로 노드id\n", " condition = u_condition[uturn_id] # 유턴조건\n", " # 바꿔서 넣을 데이터프레임\n", " uangle = self.matcher[self.matcher.parent_id==parent_id].copy() # 'angle' dataframe for a specific 'parent_id'\n", " uangle['node_id'] = uturn_id\n", " uangle['node_type'] = 'uturn'\n", " # 유턴에 관한 정보\n", " urow = self.uturn[self.uturn.node_id==uturn_id].iloc[0]\n", " node_id = urow.parent_id\n", " inc_edge_id = urow.inc_edge_id # 유턴 진입엣지id\n", " out_edge_id = urow.out_edge_id # 유턴 진출엣지id\n", " adj_inc_edge_id = urow.adj_inc_edge_id # 유턴 인접진입엣지id\n", " adj_out_edge_id = urow.adj_out_edge_id # 유턴 인접진출엣지id\n", "\n", " # 해당 노드에 대한 (노드id, 진입(출)엣지id, 방위각) 데이터프레임\n", " nia = NIA[NIA.node_id==node_id].sort_values(by='angle').reset_index(drop=True)\n", " noa = NOA[NOA.node_id==node_id].sort_values(by='angle').reset_index(drop=True)\n", "\n", " # 진입엣지 각도\n", " inc_angle = nia[nia.inc_edge_id==adj_inc_edge_id].iloc[0].angle\n", "\n", " # 진/출입로 각도 목록 (extended)\n", " inc_angles = np.array(nia.angle)\n", " inc_angles = np.concatenate((inc_angles - 360, inc_angles, inc_angles + 360))\n", " out_angles = np.array(noa.angle)\n", " out_angles = np.concatenate((out_angles - 360, out_angles, out_angles + 360))\n", "\n", " # 좌측 진출로 (좌회전신호시 진출로)\n", " out_angles_left = out_angles[out_angles >= inc_angle + self.angle_sep]\n", " out_angle_left = np.sort(out_angles_left)[0] % 360\n", " out_edge_id_left = noa[noa.angle==out_angle_left].iloc[0].out_edge_id\n", "\n", " # 좌측 진입로 (보행신호시 진입로)\n", " inc_angles_left = inc_angles[inc_angles >= inc_angle + self.angle_sep]\n", " inc_angle_left = np.sort(inc_angles_left)[0] % 360\n", " inc_edge_id_left = nia[nia.angle==inc_angle_left].iloc[0].inc_edge_id\n", "\n", " # 우측 진출로 (보행신호시 진출로)\n", " out_angles_right = out_angles[out_angles <= inc_angle - self.angle_sep]\n", " out_angle_right = np.sort(out_angles_right)[-1] % 360\n", " out_edge_id_right = noa[noa.angle==out_angle_right].iloc[0].out_edge_id\n", "\n", " # 좌회전시 조건\n", " left_flag = (uangle.inc_edge_id==adj_inc_edge_id) & (uangle.out_edge_id==out_edge_id_left) & (uangle.turn_type=='L')\n", "\n", " # 보행신호시 조건\n", " pedes_flag = (uangle.inc_edge_id==inc_edge_id_left) & (uangle.out_edge_id==out_edge_id_right)\n", "\n", " # 진출엣지가 유턴 인접진출엣지와 다르다는 조건\n", " out_adj_coincides = uangle[uangle.out_edge_id==adj_out_edge_id]\n", " out_adj_flag = ~uangle.phase_no.isin(out_adj_coincides.phase_no)\n", "\n", " if condition == '좌회전시':\n", " # 유턴교차로에 대한 from / to 재입력\n", " uangle.loc[left_flag, ['inc_edge_id', 'out_edge_id']] = [inc_edge_id, out_edge_id]\n", " uangle.loc[~left_flag, ['inc_edge_id', 'out_edge_id']] = ['', '']\n", "\n", " elif condition == '보행신호시':\n", " # 유턴교차로에 대한 from / to 재입력\n", " uangle.loc[pedes_flag, ['inc_edge_id', 'out_edge_id']] = [inc_edge_id, out_edge_id]\n", " uangle.loc[~pedes_flag, ['inc_edge_id', 'out_edge_id']] = ['', '']\n", "\n", " uturn_not_assigned = (uangle[['inc_edge_id', 'out_edge_id']] == ('', '')).any(axis=1).all()\n", " if uturn_not_assigned:\n", " if left_flag.any():\n", " uangle.loc[left_flag, ['inc_edge_id', 'out_edge_id']] = [inc_edge_id, out_edge_id]\n", " uangle.loc[~left_flag, ['inc_edge_id', 'out_edge_id']] = ['', '']\n", " elif pedes_flag.any():\n", " uangle.loc[left_flag, ['inc_edge_id', 'out_edge_id']] = [inc_edge_id, out_edge_id]\n", " uangle.loc[~left_flag, ['inc_edge_id', 'out_edge_id']] = ['', '']\n", " elif out_adj_flag.any():\n", " uangle.loc[out_adj_flag, ['inc_edge_id', 'out_edge_id']] = [inc_edge_id, out_edge_id]\n", " uangle.loc[~out_adj_flag, ['inc_edge_id', 'out_edge_id']] = ['', '']\n", " uangles.append(uangle)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
inter_noparent_idnode_idSTOS_NOphase_noring_typeinc_edge_idout_edge_idturn_typenode_type
0178i3c3001ANaNcoord
1178i3c3001BNaNcoord
2178i3c3002A571542116_01-571542116_02.96Scoord
3178i3c3002B571542116_02.96571542116_02.164Scoord
4178i3c3003A571542116_01-571542116_02.96Scoord
5178i3c3003B571542116_02.96571542116_02.164Scoord
6178i3c3004A571542116_01-571542116_02.96Scoord
7178i3c3004B571542116_02.96571542116_02.164Scoord
\n", "
" ], "text/plain": [ " inter_no parent_id node_id STOS_NO phase_no ring_type inc_edge_id \\\n", "0 178 i3 c30 0 1 A \n", "1 178 i3 c30 0 1 B \n", "2 178 i3 c30 0 2 A 571542116_01 \n", "3 178 i3 c30 0 2 B 571542116_02.96 \n", "4 178 i3 c30 0 3 A 571542116_01 \n", "5 178 i3 c30 0 3 B 571542116_02.96 \n", "6 178 i3 c30 0 4 A 571542116_01 \n", "7 178 i3 c30 0 4 B 571542116_02.96 \n", "\n", " out_edge_id turn_type node_type \n", "0 NaN coord \n", "1 NaN coord \n", "2 -571542116_02.96 S coord \n", "3 571542116_02.164 S coord \n", "4 -571542116_02.96 S coord \n", "5 571542116_02.164 S coord \n", "6 -571542116_02.96 S coord \n", "7 571542116_02.164 S coord " ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 연등교차로 처리\n", "self.coord[['inc_edge_id', 'out_edge_id']] = self.coord[['inc_edge_id', 'out_edge_id']].fillna('')\n", "self.coord['node_type'] = 'coord'\n", "self.coord" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
inter_nophase_noring_typeSTOS_NOnode_idparent_idinc_edge_idout_edge_idturn_typenode_type
01751A0i0i0-571542797_02571500487_01Snormal
11751B0i0i0-571500487_01571542797_02Snormal
21752A0i0i0-571500487_01571545870_01Lnormal
31752B0i0i0-571542797_02571510153_01Lnormal
41753A0i0i0571545870_02571510153_01Snormal
51753B0i0i0571545870_02571542797_02Lnormal
61754A0i0i0571510153_02571500487_01Lnormal
71754B0i0i0571510153_02571545870_01Snormal
161761A0i1i1-571542810_01-571542797_02.99Snormal
171761B0i1i1571542797_02.99571542810_01Snormal
181762A0i1i1-571542810_01-571542797_02.99Snormal
191762B0i1i1-571542810_01571543469_01Lnormal
201763A0i1i1571543469_02-571542797_02.99Lnormal
211763B0i1i1NaNNaNNaNnormal
321771A0i2i2-571542809_01571542811_01Snormal
331771B0i2i2571542811_02571542809_01Snormal
341772A0i2i2-571542809_01571542811_01Snormal
351772B0i2i2571542811_02571542809_01Snormal
361773A0i2i2NaNNaNNaNnormal
371773B0i2i2NaNNaNNaNnormal
381774A0i2i2-571542809_01571542811_01Snormal
391774B0i2i2571542811_02571542809_01Snormal
481781A0i3i3571540304_02571556450_01Snormal
491781B0i3i3571556450_02571540304_01Snormal
501782A0i3i3571556450_02571500475_01Lnormal
511782B0i3i3571540304_02571540303_01Lnormal
521783A0i3i3571540303_02.21571556450_01Lnormal
531783B0i3i3571540303_02.21571500475_01Snormal
541784A0i3i3-571500475_01571540303_01Snormal
551784B0i3i3-571500475_01571540304_01Lnormal
642011A0i8i8-571500569_01571500583_02Snormal
652011B0i8i8-571500569_01571500618_01Lnormal
662012A0i8i8571500618_02571500583_02Lnormal
672012B0i8i8571500618_02571500617_01Snormal
682013A0i8i8571500617_02571500618_01Snormal
692013B0i8i8571500618_02571500617_01Snormal
702014A0i8i8571500617_02571500618_01Snormal
712014B0i8i8571500617_02571500569_01Lnormal
722015A0i8i8571500583_01571500617_01Lnormal
732015B0i8i8571500583_01571500569_01Snormal
802021A0i9i9571510152_02-571510152_01Snormal
812021B0i9i9571510152_01571510152_01.65Snormal
822022A0i9i9NaNNaNNaNnormal
832022B0i9i9NaNNaNNaNnormal
962061A0i7i7-571511538_02571542073_02Snormal
972061B0i7i7571542073_01571511538_02Snormal
982062A0i7i7NaNNaNNaNnormal
992062B0i7i7NaNNaNNaNnormal
1002063A0i7i7-571511538_02571542073_02Snormal
1012063B0i7i7571542073_01571511538_02Snormal
1022064A0i7i7NaNNaNNaNnormal
1032064B0i7i7NaNNaNNaNnormal
1122101A0i6i6-571542115_01571500535_01Snormal
1132101B0i6i6NaNNaNNaNnormal
1142102A0i6i6571500535_02.18571511538_01Lnormal
1152102B0i6i6571500535_02.18571542115_01Snormal
1162103A0i6i6571511538_02.121571542115_01Lnormal
1172103B0i6i6571511538_02.121571500585_01Snormal
1182104A0i6i6571500585_02571511538_01Snormal
1192104B0i6i6571500585_02571500535_01Lnormal
\n", "
" ], "text/plain": [ " inter_no phase_no ring_type STOS_NO node_id parent_id \\\n", "0 175 1 A 0 i0 i0 \n", "1 175 1 B 0 i0 i0 \n", "2 175 2 A 0 i0 i0 \n", "3 175 2 B 0 i0 i0 \n", "4 175 3 A 0 i0 i0 \n", "5 175 3 B 0 i0 i0 \n", "6 175 4 A 0 i0 i0 \n", "7 175 4 B 0 i0 i0 \n", "16 176 1 A 0 i1 i1 \n", "17 176 1 B 0 i1 i1 \n", "18 176 2 A 0 i1 i1 \n", "19 176 2 B 0 i1 i1 \n", "20 176 3 A 0 i1 i1 \n", "21 176 3 B 0 i1 i1 \n", "32 177 1 A 0 i2 i2 \n", "33 177 1 B 0 i2 i2 \n", "34 177 2 A 0 i2 i2 \n", "35 177 2 B 0 i2 i2 \n", "36 177 3 A 0 i2 i2 \n", "37 177 3 B 0 i2 i2 \n", "38 177 4 A 0 i2 i2 \n", "39 177 4 B 0 i2 i2 \n", "48 178 1 A 0 i3 i3 \n", "49 178 1 B 0 i3 i3 \n", "50 178 2 A 0 i3 i3 \n", "51 178 2 B 0 i3 i3 \n", "52 178 3 A 0 i3 i3 \n", "53 178 3 B 0 i3 i3 \n", "54 178 4 A 0 i3 i3 \n", "55 178 4 B 0 i3 i3 \n", "64 201 1 A 0 i8 i8 \n", "65 201 1 B 0 i8 i8 \n", "66 201 2 A 0 i8 i8 \n", "67 201 2 B 0 i8 i8 \n", "68 201 3 A 0 i8 i8 \n", "69 201 3 B 0 i8 i8 \n", "70 201 4 A 0 i8 i8 \n", "71 201 4 B 0 i8 i8 \n", "72 201 5 A 0 i8 i8 \n", "73 201 5 B 0 i8 i8 \n", "80 202 1 A 0 i9 i9 \n", "81 202 1 B 0 i9 i9 \n", "82 202 2 A 0 i9 i9 \n", "83 202 2 B 0 i9 i9 \n", "96 206 1 A 0 i7 i7 \n", "97 206 1 B 0 i7 i7 \n", "98 206 2 A 0 i7 i7 \n", "99 206 2 B 0 i7 i7 \n", "100 206 3 A 0 i7 i7 \n", "101 206 3 B 0 i7 i7 \n", "102 206 4 A 0 i7 i7 \n", "103 206 4 B 0 i7 i7 \n", "112 210 1 A 0 i6 i6 \n", "113 210 1 B 0 i6 i6 \n", "114 210 2 A 0 i6 i6 \n", "115 210 2 B 0 i6 i6 \n", "116 210 3 A 0 i6 i6 \n", "117 210 3 B 0 i6 i6 \n", "118 210 4 A 0 i6 i6 \n", "119 210 4 B 0 i6 i6 \n", "\n", " inc_edge_id out_edge_id turn_type node_type \n", "0 -571542797_02 571500487_01 S normal \n", "1 -571500487_01 571542797_02 S normal \n", "2 -571500487_01 571545870_01 L normal \n", "3 -571542797_02 571510153_01 L normal \n", "4 571545870_02 571510153_01 S normal \n", "5 571545870_02 571542797_02 L normal \n", "6 571510153_02 571500487_01 L normal \n", "7 571510153_02 571545870_01 S normal \n", "16 -571542810_01 -571542797_02.99 S normal \n", "17 571542797_02.99 571542810_01 S normal \n", "18 -571542810_01 -571542797_02.99 S normal \n", "19 -571542810_01 571543469_01 L normal \n", "20 571543469_02 -571542797_02.99 L normal \n", "21 NaN NaN NaN normal \n", "32 -571542809_01 571542811_01 S normal \n", "33 571542811_02 571542809_01 S normal \n", "34 -571542809_01 571542811_01 S normal \n", "35 571542811_02 571542809_01 S normal \n", "36 NaN NaN NaN normal \n", "37 NaN NaN NaN normal \n", "38 -571542809_01 571542811_01 S normal \n", "39 571542811_02 571542809_01 S normal \n", "48 571540304_02 571556450_01 S normal \n", "49 571556450_02 571540304_01 S normal \n", "50 571556450_02 571500475_01 L normal \n", "51 571540304_02 571540303_01 L normal \n", "52 571540303_02.21 571556450_01 L normal \n", "53 571540303_02.21 571500475_01 S normal \n", "54 -571500475_01 571540303_01 S normal \n", "55 -571500475_01 571540304_01 L normal \n", "64 -571500569_01 571500583_02 S normal \n", "65 -571500569_01 571500618_01 L normal \n", "66 571500618_02 571500583_02 L normal \n", "67 571500618_02 571500617_01 S normal \n", "68 571500617_02 571500618_01 S normal \n", "69 571500618_02 571500617_01 S normal \n", "70 571500617_02 571500618_01 S normal \n", "71 571500617_02 571500569_01 L normal \n", "72 571500583_01 571500617_01 L normal \n", "73 571500583_01 571500569_01 S normal \n", "80 571510152_02 -571510152_01 S normal \n", "81 571510152_01 571510152_01.65 S normal \n", "82 NaN NaN NaN normal \n", "83 NaN NaN NaN normal \n", "96 -571511538_02 571542073_02 S normal \n", "97 571542073_01 571511538_02 S normal \n", "98 NaN NaN NaN normal \n", "99 NaN NaN NaN normal \n", "100 -571511538_02 571542073_02 S normal \n", "101 571542073_01 571511538_02 S normal \n", "102 NaN NaN NaN normal \n", "103 NaN NaN NaN normal \n", "112 -571542115_01 571500535_01 S normal \n", "113 NaN NaN NaN normal \n", "114 571500535_02.18 571511538_01 L normal \n", "115 571500535_02.18 571542115_01 S normal \n", "116 571511538_02.121 571542115_01 L normal \n", "117 571511538_02.121 571500585_01 S normal \n", "118 571500585_02 571511538_01 S normal \n", "119 571500585_02 571500535_01 L normal " ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "self.matcher = pd.concat([self.matcher, *uangles, self.coord])\n", "self.matcher = self.matcher.drop(columns=['inc_angle', 'out_angle'])\n", "self.matcher[:60]" ] } ], "metadata": { "kernelspec": { "display_name": "siggen", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 2 }