junggilpark 9 сар өмнө
parent
commit
1faf53edc0

+ 1 - 1
conf/tsi-sig-server.pid

@@ -1 +1 @@
-50816
+36064

+ 1 - 0
src/main/java/com/tsi/sig/server/mapper/MainMapper.java

@@ -16,6 +16,7 @@ public interface MainMapper {
     List<EvpServiceVo> getEvpServiceList();
     List<EvpServiceVo> getEvpHistoryList(Map<String, Object> vo);
     List<EvpRouteVo> getEvpRouteList(Map<String, Object> vo);
+    List<EvpNodeVo> getEvpNodeList(Map<String, Object> vo);
     List<EvpPhaseVo> getEvpPhaseList(HashMap<String, Object> vo);
     List<EvpSignalVo> getEvpSignalList(Map<String, Object> vo);
     List<EvpEventVo> getEvpEventList(Map<String, Object> paramMap);

+ 2 - 1
src/main/java/com/tsi/sig/server/service/MainService.java

@@ -189,12 +189,12 @@ public class MainService {
 
     public List<EvpServiceVo> getEvpServiceList() {
         List<EvpServiceVo> result = this.mainMapper.getEvpServiceList();
-        //log.info("{}", );
         if (result.size() > 0) {
             for (EvpServiceVo vo : result) {
                 Map<String, Object> paramMap = new HashMap<>();
                 paramMap.put("serviceId", vo.getServiceId());
                 vo.setRouteList(this.mainMapper.getEvpRouteList(paramMap));
+                vo.setNodeList(this.mainMapper.getEvpNodeList(paramMap));
                 vo.setPhaseList(this.mainMapper.getEvpSignalCurrList(paramMap));
                 vo.setEventList(this.mainMapper.getEvpEventCurrList(paramMap));
             }
@@ -218,6 +218,7 @@ public class MainService {
                 Map<String, Object> param = new HashMap<>();
                 param.put("serviceId", vo.getServiceId());
                 vo.setRouteList(this.mainMapper.getEvpRouteList(param));
+                vo.setNodeList(this.mainMapper.getEvpNodeList(param));
                 vo.setPhaseList(this.mainMapper.getEvpSignalList(param));
                 vo.setEventList(this.mainMapper.getEvpEventList(param));
             }

+ 25 - 0
src/main/java/com/tsi/sig/server/vo/EvpNodeVo.java

@@ -0,0 +1,25 @@
+package com.tsi.sig.server.vo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class EvpNodeVo {
+    @JsonProperty("service_id")
+    private String serviceId;
+
+    @JsonProperty("seq_no")
+    private Integer seqNo;
+
+    @JsonProperty("node_id")
+    private Long nodeId;
+
+    @JsonProperty("node_nm")
+    private String nodeNm;
+
+    @JsonProperty("lat")
+    private Double lat;
+
+    @JsonProperty("lng")
+    private Double lng;
+}

+ 3 - 0
src/main/java/com/tsi/sig/server/vo/EvpServiceVo.java

@@ -52,6 +52,9 @@ public class EvpServiceVo {
     @JsonProperty("route_list")
     private List<EvpRouteVo> routeList;
 
+    @JsonProperty("node_list")
+    private List<EvpNodeVo> nodeList;
+
     @JsonProperty("phase_list")
     private List<EvpSignalVo> phaseList;
 

+ 13 - 0
src/main/resources/mybatis/mapper/main.xml

@@ -91,6 +91,19 @@
 		  AND LNG BETWEEN 125.06666667 AND 131.87222222
 	</select>
 
+	<select id="getEvpNodeList" parameterType="java.util.HashMap" resultType="com.tsi.sig.server.vo.EvpNodeVo">
+		SELECT SERVICE_ID,
+			   SEQ_NO,
+		       NODE_ID,
+		       NODE_NM,
+			   LAT,
+			   LNG
+		FROM tb_evp_node
+		WHERE SERVICE_ID = #{serviceId}
+		  AND LAT BETWEEN 33.10000000 AND 38.45000000
+		  AND LNG BETWEEN 125.06666667 AND 131.87222222
+	</select>
+
 	<select id="getEvpPhaseList" parameterType="java.util.HashMap" resultType="com.tsi.sig.server.vo.EvpPhaseVo">
 		SELECT SERVICE_ID,
 			   SEQ_NO,

+ 26 - 8
src/main/resources/static/css/main.css

@@ -89,7 +89,7 @@ table {
 
 #mapWrapper {
     width: calc(100% - 338px);
-    height: calc(75% - 10px);
+    height: calc(100% - 230px);
     /* border-right: 1px solid #bbb;
     border-top: 1px solid #bbb;
     border-left: 1px solid #bbb; */
@@ -606,7 +606,7 @@ body, button, input, select, td, textarea, th {
     display: none;
     position: absolute;
     top: 30px;
-    left: 10px;
+    left: 30px;
     bottom: 0;
     width: 260px;
     /*border: 1px solid #4b9fbf;*/
@@ -1149,12 +1149,13 @@ div.popState dl.jaywork dd strong.rblue {color:#607cd4; text-align:right;}
 .iframeBottomList {
     margin-left: 2px;
     width: calc(100% - 337px);
-    height: calc(100% - 75%);
+    height: 222px;
     overflow: hidden;
     border: 0;
     border-bottom: 1px solid #595959;
     border-left: 1px solid #595959;
-    z-index: 50
+    z-index: 50;
+    box-sizing: border-box;
 }
 
 .leftMenu {
@@ -1368,11 +1369,14 @@ select#region {
     cursor: default;
 }
 .leftMenu #historyList .empty-history {
-    height: 449px;
+    height: calc(100% - 21.3px);
     background-color: #292828;
     text-align: center;
     color: #b2b2b2;
     cursor: default;
+    display: flex;
+    align-items: center;
+    justify-content: center;
 }
 
 .leftMenu #historyList thead th:not(:last-child),
@@ -1598,6 +1602,7 @@ select#region {
 
 .bottomMenu .bottomBody table {
     width: 100%;
+    min-width: 1500px;
 }
 
 .bottomMenu .bottomBody table tbody {
@@ -3183,6 +3188,13 @@ cursor: pointer;border: 1px solid #ebebeb;border-bottom-color: #e2e2e2;border-ra
     top: 80px;
     left: 45%
 }
+.history-loading{
+    width: 100%;
+    height: calc(100% - 21.3px);
+    display: none;
+    align-items: center;
+    justify-content: center;
+}
 
 /* 로드뷰 아이콘 */
 
@@ -3283,6 +3295,12 @@ cursor: pointer;border: 1px solid #ebebeb;border-bottom-color: #e2e2e2;border-ra
 #cvibBottomInfo > tr:nth-child(even) {
     background-color: #3c3c3c;
 }
+
+#evpBottomInfo > tr {
+    box-sizing: border-box;
+    height: 32px;
+    cursor: pointer;
+}
 .bottomMenu .bottomBody table tr.on td {
     color: #eeeeee;
 }
@@ -3417,7 +3435,6 @@ cursor: pointer;border: 1px solid #ebebeb;border-bottom-color: #e2e2e2;border-ra
     margin: 0 auto
 }
 
-
 .coordBox {
     background-color: white;
     gap: 10px;
@@ -3428,7 +3445,7 @@ cursor: pointer;border: 1px solid #ebebeb;border-bottom-color: #e2e2e2;border-ra
     justify-content: center;
     position: absolute;
     z-index: 2;
-    left: 85px;
+    left: 115px;
     top: 28px;
     border: 1px solid #c9c9c9;
 }
@@ -3536,7 +3553,7 @@ cursor: pointer;border: 1px solid #ebebeb;border-bottom-color: #e2e2e2;border-ra
 }
 .evp_legend {
     position: absolute;
-    bottom: calc(100% - 75%  + 7px);
+    bottom: 228px;
     right: 50px;
     z-index: 20;
     display: none;
@@ -3548,6 +3565,7 @@ select#region,
 #searchText,
 #start,
 #end,
+.coordBox,
 #start_time, #end_time,
 #map .mapToggle img,
 img[src="images/tab01_on.png"],

+ 1 - 1
src/main/resources/static/js/common/common-ajax.js

@@ -1 +1 @@
-/**
 * @param {Object} url
 * @param {Object} param
 * @param {Object} callback
 */
function requestService(url, param, callback, async) {
    /*
     * 스프링시큐리티 csrf 토큰 에러때문에  ajax 통신시 해더에 포함해줘야한다.
     */

    if (async) {

        async = true;
    }

    const token = $("meta[name='_csrf']").attr("content");
    const header = $("meta[name='_csrf_header']").attr("content");
    $.ajax({
        url: _WEB_CONTEXT_NAME + "/" + url
        , data: param
        , cache: false
        , async: async
        , type: 'POST'
        , success: callback
        , error: whenError
        , beforeSend: function (xhr) {
            xhr.setRequestHeader(header, token);
        }
    });
}

/**
 *
 * @param url
 * @param formName
 * @param callback
 * @param async
 */
function requestServiceForm(url, formName, callback, async) {

    if (async) {

        async = true;
    }

    $("#" + formName).ajaxSubmit({

        url: _WEB_CONTEXT_NAME + "/" + url
        , data: $(this).serialize()
        , cache: false
        , async: async
        , type: 'POST'
        , success: callback
        , error: whenError

    });
}

function whenError() {

    console.log('ajax error');

}
+/**
 * @param {Object} url
 * @param {Object} param
 * @param {Object} callback
 */
function requestService(url, param, callback, async, errCallback) {
    /*
     * 스프링시큐리티 csrf 토큰 에러때문에  ajax 통신시 해더에 포함해줘야한다.
     */

    if (async) {

        async = true;
    }

    const token = $("meta[name='_csrf']").attr("content");
    const header = $("meta[name='_csrf_header']").attr("content");
    $.ajax({
        url: _WEB_CONTEXT_NAME + "/" + url
        , data: param
        , cache: false
        , async: async
        , type: 'POST'
        , success: callback
        , error: (err)=>{
            if (errCallback) {
                errCallback(err);
            }
            else {
                whenError();
            }
        }
        , beforeSend: function (xhr) {
            xhr.setRequestHeader(header, token);
        }
    });
}

/**
 *
 * @param url
 * @param formName
 * @param callback
 * @param async
 */
function requestServiceForm(url, formName, callback, async) {

    if (async) {

        async = true;
    }

    $("#" + formName).ajaxSubmit({

        url: _WEB_CONTEXT_NAME + "/" + url
        , data: $(this).serialize()
        , cache: false
        , async: async
        , type: 'POST'
        , success: callback
        , error: whenError

    });
}

function whenError() {

    console.log('ajax error');

}

+ 43 - 112
src/main/resources/static/js/map.js

@@ -177,6 +177,8 @@ function init() {
                iframe.find('.tr_'+ serviceId).addClass('on');
            }
            let idx = _evpData.findIndex((obj)=> obj.service_id === serviceId);
+           const bottomEl = iframeContent.getBottomListEl(_evpData[idx], _evpData[idx].event_list[0], 0);
+            $('#iframeBottomList').contents().find('#evpBottomInfo').html(bottomEl);
            drawEmergencyRoad(_evpData[idx]);
         }
     }
@@ -223,113 +225,6 @@ function getKakaoPosition(y, x) {
     return new kakao.maps.LatLng(y, x);
 }
 
-function emergencyHistory() {
-    const param = {serviceId : '1832024101614555100'};
-     requestService('getEvpHistory.do', param, emergencyHistoryCallBack, true);
-}
-
-const historyMap = new Map();
-function emergencyHistoryCallBack(data) {
-    if (data && data.length) {
-
-        data.forEach((obj)=>{
-            historyMap.set(obj.service_id, obj);
-        })
-
-        const selected = historyMap.get('1832024101614555100');
-        const {route_list, phase_list, event_list} = selected;
-        const phaseMap = new Map();
-
-        const dateMap = new Map();
-
-        event_list.forEach((eventObj)=>{
-            dateMap.set(eventObj.clct_dt, new Map());
-            dateMap.get(eventObj.clct_dt).set('event', eventObj);
-            dateMap.get(eventObj.clct_dt).set('sig', []);
-        });
-
-        phase_list.forEach((obj)=>{
-            if (!dateMap.get(obj.clct_dt)) {
-                dateMap.set(obj.clct_dt, new Map());
-                dateMap.get(obj.clct_dt).set('event', null);
-                dateMap.get(obj.clct_dt).set('sig', []);
-            }
-            dateMap.get(obj.clct_dt).get('sig').push(obj);
-        });
-
-        let beforeSig;
-        let beforeEvent;
-        const dateArr = Array.from(dateMap.keys()).sort();
-
-
-        let road = null;
-        let car = null;
-        let sig = new Map();
-        let arrive = null;
-
-        for (let date of dateArr) {
-            const objMap = dateMap.get(date);
-            if (objMap.get('event') === null) {
-                if (beforeEvent === undefined) {
-                    let idx = dateArr.findIndex(key => dateMap.get(key).get('event') !== null);
-                    let key = dateArr[idx];
-                    beforeEvent = dateMap.get(key).get('event');
-                }
-                dateMap.get(date).set('event', beforeEvent);
-            }
-            else {
-                beforeEvent = objMap.get('event');
-            }
-
-            if (objMap.get('sig') === null) {
-                if (beforeSig === undefined) {
-                    for (let key of dateArr) {
-                        if (dateMap.get(key).get('sig').size() !== 0) {
-                            beforeSig = dateMap.get(key).get('sig');
-                            break;
-                        }
-                    }
-                }
-                dateMap.get(date).set('sig', beforeSig);
-            }
-            else {
-                beforeSig = objMap.get('sig');
-            }
-        }
-        const sigArr = dateMap.get(dateArr[0]).get('sig');
-        const eventObj = dateMap.get(dateArr[0]).get('event');
-
-
-
-        if (route_list && route_list.length) {
-           road = new EmergencyRoadObj(route_list);
-           road.setBound();
-        }
-
-
-        sigArr.forEach((obj)=>{
-            sig.set(obj.service_id + '_' + obj.seq_no, new EmergencySig(obj));
-        });
-
-
-        if (selected.arr_lat && selected.arr_lng) {
-            arrive = new EmergencyMarker(getKakaoPosition(selected.arr_lat, selected.arr_lng), '/images/evp_arr.svg');
-        }
-        car = new EmergencyMarker(getKakaoPosition(eventObj.cur_lat, eventObj.cur_lng), '/images/car.svg');
-
-        let cnt = 1;
-        let timer = setInterval(()=>{
-            if (cnt === dateArr.length) {
-                return clearInterval(timer);
-            }
-            const objMap = dateMap.get(dateArr[cnt]);
-            setEmergencyCurr(car, sig, road, arrive, objMap);
-            cnt++;
-        }, 500);
-
-    }
-}
-
 function drawHistory(data) {
     return new EmergencyObj(data);
 }
@@ -1845,12 +1740,7 @@ class EmergencySig{
 
         this.createRing(this);
 
-        //if (map.getLevel() < 5) {
-        // this.circle.setMap(map);
-        // this.a_ring.show();
-        // this.b_ring.show();
         this.show();
-        //}
     }
 
     createRing(data) {
@@ -1956,6 +1846,47 @@ class EmergencySig{
     }
 }
 
+class EmergencyCircle{
+    constructor({service_id, seq_no, node_id, node_nm, lat, lng}) {
+        this.service_id = service_id;
+        this.seq_no = seq_no;
+        this.node_id = node_id;
+        this.node_nm = node_nm;
+        this.lat = lat;
+        this.lng = lng;
+        this.circle = null;
+    }
+
+    init() {
+        let stateColor = '#FF0000';
+        const position = getKakaoPosition(this.lat, this.lng);
+
+        this.circle = new kakao.maps.Circle({
+            center : position,  // 원의 중심좌표 입니다
+            radius: 60, // 미터 단위의 원의 반지름입니다
+            strokeWeight: 5, // 선의 두께입니다
+            strokeColor: stateColor, // 선의 색깔입니다
+            strokeOpacity: 1, // 선의 불투명도 입니다 1에서 0 사이의 값이며 0에 가까울수록 투명합니다
+            strokeStyle: 'solid', // 선의 스타일 입니다
+            fillColor: stateColor, // 채우기 색깔입니다
+            fillOpacity: 0.3, // 채우기 불투명도 입니다
+            zIndex: 2,
+            name : '신호현시',
+        });
+    }
+
+    show() {
+        if (this.circle) {
+            this.circle.setMap(map);
+        }
+    }
+
+    hide() {
+        if (this.circle) {
+            this.circle.setMap(null);
+        }
+    }
+}
 
 class EmergencyRoadObj{
     constructor(pathArr) {

+ 23 - 0
src/main/webapp/WEB-INF/jsp/bottomListFrame.jsp

@@ -92,6 +92,29 @@
         $('#cvibBottomHead').show();
         $('#cvibBottomBody').show();
     }
+
+    function moveLocation(serviceId, el) {
+        const treeFrame = $('#iframeTreeList', parent.document);
+        if (!treeFrame.contents().find('#play').hasClass('on')) {
+            return;
+        }
+
+        $('tr.on').removeClass('on');
+        $(el).addClass('on');
+        const map = treeFrame.get(0).contentWindow._historyMap.get(serviceId.toString());
+        const idx = $(el).index();
+        //$('#cvibBottomBody').scrollTop(32 * idx);
+        if (map) {
+           const obj  = map.get('obj');
+           const draw = map.get('draw');
+
+           const {cur_lat, cur_lng} = obj.event_list[idx];
+           let position = window.parent.getKakaoPosition(cur_lat, cur_lng);
+           draw.car.moveMarker(position);
+           treeFrame.get(0).contentWindow.setSigState(obj.phase_list[idx], draw);
+        }
+
+    }
 </script>
 </body>
 </html>

+ 2 - 2
src/main/webapp/WEB-INF/jsp/main.jsp

@@ -421,10 +421,10 @@
             // $('.upDownMap img').attr('src', 'images/arrow_down.png').slide;
             $('.upDownMap img').attr('src', 'images/drop_down.png').slide;
             $('.iframeBottomList').show();
-            $('#mapWrapper').css('height', 'calc(75% - 10px)');
+            $('#mapWrapper').css('height', 'calc(100% - 230px)');
             if (UserAgent.match(/iPhone|iPod|Android|Windows CE|BlackBerry|Symbian|Windows Phone|webOS|Opera Mini|Opera Mobi|POLARIS|IEMobile|lgtelecom|nokia|SonyEricsson/i) != null || UserAgent.match(/LG|SAMSUNG|Samsung/) != null)
             $('.mylocationMob').css('bottom', 'calc(25% + 35px)');
-            $('.evp_legend').css('bottom', 'calc(100% - 75%  + 7px)');
+            $('.evp_legend').css('bottom', '228px');
             upDownFlag = false;
         }
         map.relayout();

+ 183 - 100
src/main/webapp/WEB-INF/jsp/treeListFrame.jsp

@@ -15,8 +15,8 @@
 <body class="sang" style="min-width:305px !important;overflow:hidden">
 <div class="leftMenu">
     <ul class="tabs">
-        <li class="on"><div onclick="goIntMenu()" id="tab1"><img src="/images/int_icon.png" width="17px">교차로</div></li>
-        <li><div onclick="goEvpMenu()" id="tab2"><img src="/images/evp_icon.png" width="17px">긴급차량우선신호</div></li>
+        <li class="on" onclick="goIntMenu()"><div id="tab1"><img src="/images/int_icon.png" width="17px">교차로</div></li>
+        <li onclick="goEvpMenu()"><div id="tab2"><img src="/images/evp_icon.png" width="17px">긴급차량우선신호</div></li>
 <%--        <li><a href="javascript:goIntMenu()" class="tab" id="tab1"><img src="images/tab01_on.png" alt="교차로메뉴"/></a></li>--%>
 <%--        <li><a href="javascript:goEvpMenu()" class="tab" id="tab2"><img src="images/tab02_off.png" alt="긴급차량우선신호메뉴"/></a></li>--%>
 <%--        <div style="position: absolute; left: 95px; top: 5px;"><img src="images/map/btn_alert_A.gif" style="width: 22px"></div>--%>
@@ -27,14 +27,14 @@
         <div class="search-button" onclick="searchTreeData()">검색</div>
     </div>
     <div class="evp-cur">
-        <div class="title">실시간 긴급차량우선신호 리스트</div>
+        <div class="title">긴급차량우선신호 서비스 현황</div>
         <div id="evp-cur-list">
             <table>
                 <thead>
                     <tr>
-                        <th width="25%">발생시각</th>
+                        <th width="23%">발생시각</th>
                         <th width="40%">서비스 ID</th>
-                        <th width="35%">서비스명</th>
+                        <th width="37%">서비스명</th>
                     </tr>
                 </thead>
                 <tbody class="evp-table">
@@ -46,7 +46,7 @@
         </div>
     </div>
     <div class="historyBox">
-        <div class="title">긴급차량우선신호 이력조회</div>
+        <div class="title">긴급차량우선신호 서비스 이력</div>
         <div>
             <div>
                 <div>지역</div>
@@ -75,17 +75,15 @@
         <table>
             <thead>
                 <tr>
-                    <th width="25%">발생시각</th>
+                    <th width="23%">발생시각</th>
                     <th width="40%">서비스 ID</th>
-                    <th width="35%">서비스명</th>
+                    <th width="37%">서비스명</th>
                 </tr>
             </thead>
-            <tbody class="history-table">
-                <tr>
-                    <td colspan="3" class="empty-history">조회된 내용이 없습니다.</td>
-                </tr>
-            </tbody>
+            <tbody class="history-table"></tbody>
         </table>
+        <div class="empty-history">조회된 내용이 없습니다.</div>
+        <div class="history-loading"><img src="/css/themes/classic/throbber.gif"/></div>
     </div>
     <div class="play-box">
         <div>
@@ -690,13 +688,20 @@
         if (_historyMap && _historyMap.size) {
             if(interval) clearInterval(interval);
             _historyMap.forEach((obj)=> {
-                if (obj.get('draw')) obj.get('draw').clear();
+                if (obj.get('draw')) {
+                    obj.get('draw').clear();
+                    obj.set('draw', null);
+                }
             });
             $('.history-table tr.on').removeClass('on');
+            const bottomFrame = $('#iframeBottomList', parent.document).contents();
+            bottomFrame.find('#cvibBottomBodyTable').show();
+            bottomFrame.find('#evpBottomBodyTable').hide();
+            bottomFrame.find('#evpBottomInfo').empty();
+            imageOnOff('play', false);
+            imageOnOff('stop', false);
+            imageOnOff('pause', false);
         }
-        const bottomFrame = $('#iframeBottomList', parent.document).contents();
-        bottomFrame.find('#cvibBottomBodyTable').show();
-        bottomFrame.find('#evpBottomBodyTable').hide();
         //.show();
     }
 
@@ -725,6 +730,8 @@
     }
 
     function searchHistoryData() {
+        const $emptyBox = $('.empty-history');
+        const $historyLoading = $('.history-loading');
         const $region       = $('#region');
         const regionVal    = $region.val();
         const startVal     = $start.val();
@@ -758,12 +765,30 @@
             alert('검색시작 일시가 종료 일시 보다 큽니다.');
             return $end.focus();
         }
-        window.parent.requestService('getEvpHistory.do', param, (rec)=>{
-            const $historyTable = $('.history-table');
-            let str = '<tr><td colspan="3" class="empty-history">조회된 내용이 없습니다.</td></tr>'
+
+        const $historyTable = $('.history-table');
+        $historyTable.empty();
+        $emptyBox.css('display', 'none');
+        $historyLoading.css('display', 'flex');
+        imageOnOff('play', false);
+        imageOnOff('stop', false);
+        imageOnOff('pause', false);
+        if (_historyMap.size) {
+            _historyMap.forEach((obj)=>{
+                if (obj.get('draw')) obj.get('draw').clear();
+            });
             _historyMap.clear();
+            if (interval) {
+                clearInterval(interval);
+                interval = null;
+                cnt = 0;
+            }
+            const bottomFrame = $('#iframeBottomList', parent.document).contents();
+            bottomFrame.find('#evpBottomInfo').empty();
+        }
+        window.parent.requestService('getEvpHistory.do', param, (rec)=>{
+            let str = ''
             if (rec && rec.length) {
-                str = '';
                 for (let history of rec) {
                      _historyMap.set(history.service_id, new Map());
                     const {event_list, phase_list} = history;
@@ -800,7 +825,9 @@
                                 else if (objMap.get('event')){
                                     beforeEvent = objMap.get('event');
                                 }
-                                beforeEvent.clct_dt = date;
+                                const eventData = {...beforeEvent};
+
+                                eventData.clct_dt = date;
 
                                 if (objMap.get('sig').length === 0 && beforeSig === undefined) {
                                     beforeSig = dateMap.get(phase_list[0].clct_dt).get('sig');
@@ -808,13 +835,14 @@
                                 else if (objMap.get('sig').length) {
                                     beforeSig = objMap.get('sig');
                                 }
+                                const sigData = [...beforeSig]
 
-                                beforeSig.forEach((obj)=>{
+                                sigData.forEach((obj)=>{
                                     obj.clct_dt = date;
                                 })
 
-                                history.event_list.push(beforeEvent);
-                                history.phase_list.push(beforeSig);
+                                history.event_list.push(eventData);
+                                history.phase_list.push(sigData);
                             }
                         }
                     }
@@ -823,8 +851,18 @@
                     str += `<tr class="hs_\${history.service_id}" onclick="drawHistory('\${history.service_id}')"><td>\${history.clct_dt}</td><td>\${history.service_id}</td><td>\${history.service_nm}</td></tr>`;
                 }
             }
+
+            $historyLoading.css('display', 'none');
+
+            if (str === '') {
+                $emptyBox.css('display', 'flex');
+            }
+
             $historyTable.html(str);
-        }, true);
+        }, true, ()=>{
+            $emptyBox.css('display', 'flex');
+            $historyLoading.css('display', 'none');
+        });
     }
 
     function drawHistory(serviceId) {
@@ -853,7 +891,7 @@
         $('#historyList tr.on').removeClass('on');
         $('.hs_' + serviceId).addClass('on');
         const drawObj = {...obj};
-        const {event_list, phase_list} = obj;
+        const { event_list, phase_list } = obj;
 
         if (event_list && event_list.length) drawObj.event_list = [drawObj.event_list[0]];
         if (phase_list && phase_list.length) drawObj.phase_list = phase_list[0];
@@ -861,38 +899,15 @@
         const bottomInfo  = bottomFrame.find('#evpBottomInfo');
 
         let str = '';
+
         event_list.forEach((event, idx)=>{
-            const regionNm = $('#region option[value="'+serviceId.substr(0,3)+'"]').text();
-            let className = '';
-            if (idx === 0) {
-                className = 'on';
-            }
-            let curSpd = '-';
-            if (event.cur_spd) {
-                curSpd = event.cur_spd.toLocaleString('ko-KR');
-            }
+            str += getBottomListEl(obj, event, idx);
+        });
 
-            let remDist = '-';
-            if (event.rem_dist) {
-                remDist = event.rem_dist.toLocaleString('ko-KR');
-            }
-            let clctDt = event.clct_dt.replace(/\:|\-| /gi, '')
-            str += '<tr class="'+className+'" id="'+obj.service_id+'_'+clctDt+'">'
-                        +'<td>'+event.clct_dt+'</td>'
-                        +'<td>'+regionNm+'</td>'
-                        +'<td>'+obj.service_id+'</td>'
-                        +'<td>'+obj.service_nm+'</td>'
-                        +'<td>'+obj.ev_no+'</td>'
-                        +'<td>'+event.cur_lng+'</td>'
-                        +'<td>'+event.cur_lat+'</td>'
-                        +'<td>'+curSpd+'</td>'
-                        +'<td>'+remDist+'</td>'
-                    +'</tr>';
-        })
         bottomInfo.html(str);
         const draw = window.parent.drawHistory(drawObj);
         _historyMap.get(serviceId).set('draw', draw);
-
+        bottomFrame.find('.bottomBody').scrollTop(0);
         imageOnOff('play', true);
         imageOnOff('stop', false);
         imageOnOff('pause', false);
@@ -908,6 +923,53 @@
         }
     }
 
+    /**
+     * 긴급차량우선신호 bottom list 생성
+     * @param obj 서비스 정보
+     * @param event 발생 이벤트 정보
+     * @param idx 인덱스 번호
+     * @returns {string} html 리스트 목록 반환
+     */
+    function getBottomListEl(obj, event, idx) {
+        const regionNm = $('#region option[value="'+obj.service_id.substr(0,3)+'"]').text(); //왼쪽 3글자는 센터 번호를 의미
+        let className = '';
+
+        if (idx === 0) { //처음 그릴때 첫줄을 기반으로 그리므로 선택 표시
+            className = 'on';
+        }
+        let curSpd = '-';
+        if (event.cur_spd) { // 현재 스피드가 있을경우 천단위 콤마
+            curSpd = event.cur_spd.toLocaleString('ko-KR');
+        }
+
+        let remDist = '-';
+        if (event.rem_dist) {// 남은 거리가 있을경우 천단위 콤마
+            remDist = event.rem_dist.toLocaleString('ko-KR');
+        }
+
+
+
+        let clctDt = event.clct_dt.replace(/\:|\-| /gi, '') //시간 기호(':', '-', ' ') 제거 후 값만 추출 하여 table 로우 ID 부여
+        let str = '<tr onclick="moveLocation('+obj.service_id+', this)" class="'+className+'" id="'+obj.service_id+'_'+clctDt+'">'
+                    +'<td>'+event.clct_dt+'</td>'
+                    +'<td>'+regionNm+'</td>'
+                    +'<td>'+obj.service_id+'</td>'
+                    +'<td>'+obj.service_nm+'</td>'
+                    +'<td>'+obj.ev_no+'</td>'
+                    +'<td>'+event.cur_lng+'</td>'
+                    +'<td>'+event.cur_lat+'</td>'
+                    +'<td>'+curSpd+'</td>'
+                    +'<td>'+remDist+'</td>'
+                +'</tr>';
+        return str;
+    }
+
+    /**
+     * 시뮬레이션 재생 / 멈춤 버튼 활성화 비활성화 이미지명 가져오기
+     * @param name 요소 ID name (ID 와 이미지 이름이 동일함)
+     * @param isOn 활성화/비활성화(true/false)
+     * @returns {string} 이미지명 반환
+     */
     function getImageName(name, isOn) {
         let imgName = '/images/' + name;
         const onName = '_on';
@@ -918,83 +980,104 @@
         return imgName + extName;
     }
 
-    function playSimulation() {
-        if (!$('#play').hasClass('on')) return;
 
-        const serviceId = $('.history-table tr.on').attr('class').replace(/hs_| on/gi, '');
-        let evp = _historyMap.get(serviceId);
-        let obj = evp.get('obj');
-        let draw = evp.get('draw');
-        if (!draw) {
+    /**
+     * 시뭏레이션 재생
+     */
+    function playSimulation() {
+        if (!$('#play').hasClass('on')) return; //비활성화중일 때만 실행
+        if (interval) {
+            clearInterval(interval);
+            interval = null;
+        }
+        const serviceId = $('.history-table tr.on').attr('class').replace(/hs_| on/gi, '');// 선택 로우에서 service id 추출
+        let evp = _historyMap.get(serviceId); //Map 에 저장된 해당 service_id 데이터 정보 가져오기
+        let obj = evp.get('obj'); //object 정보
+        let draw = evp.get('draw'); // marker handler 정보
+        if (!draw) {// handler 가 없다면 생성
             drawHistory(serviceId);
             draw = evp.get('draw');
         }
 
-        const { event_list, phase_list } = obj;
+        const { event_list, phase_list } = obj; //이벤트 리스트와 신호 리스트
+
+        //버튼 활성화/비활성화 적용
         imageOnOff('stop', true);
         imageOnOff('pause', true);
         imageOnOff('play', false);
-        let top = 0;
-        interval = setInterval(()=>{
-            const position = window.parent.getKakaoPosition(event_list[cnt].cur_lat, event_list[cnt].cur_lng);
-            draw.car.moveMarker(position);
-            phase_list[cnt].forEach((sigObj)=>{
-                const evpSig = draw.sig.get(sigObj.service_id + '_' + sigObj.seq_no);
-                if (evpSig === undefined) {
-                    draw.sig.set(sigObj.service_id + '_' + sigObj.seq_no, window.parent.createSig(sigObj));
-                }
-                else {
-                    if (!evpSig.a_ring && !evpSig.b_ring) {
-                        evpSig.createRing(sigObj);
-                    }
-                    else {
-                        const {a_head_lat, a_head_lng, a_mid_lat, a_mid_lng, a_end_lat, a_end_lng,
-                            b_head_lat, b_head_lng, b_mid_lat, b_mid_lng, b_end_lat, b_end_lng} = sigObj;
-                        const aPosition = [
-                            window.parent.getKakaoPosition(a_head_lat, a_head_lng),
-                            window.parent.getKakaoPosition(a_mid_lat, a_mid_lng),
-                            window.parent.getKakaoPosition(a_end_lat, a_end_lng),
-                        ]
-                        const bPosition = [
-                            window.parent.getKakaoPosition(b_head_lat, b_head_lng),
-                            window.parent.getKakaoPosition(b_mid_lat, b_mid_lng),
-                            window.parent.getKakaoPosition(b_end_lat, b_end_lng),
-                        ]
-
-                        evpSig.a_ring.setPath(aPosition);
-                        evpSig.b_ring.setPath(bPosition);
-                    }
-                    evpSig.setState(sigObj.state);
-                }
-            });
 
+        interval = setInterval(()=>{
+            cnt++;
+            if (event_list.length === cnt) {
+                return stopSimulation();
+            }
             const bottomFrame = $('#iframeBottomList', parent.document).contents();
-            top += bottomFrame.find('#evpBottomInfo tr.on').height();
             bottomFrame.find('#evpBottomInfo tr.on').removeClass('on');
             let clctDt = event_list[cnt].clct_dt.replace(/\:|\-| /gi, '')
             const bottomInfo = bottomFrame.find('#evpBottomInfo #'+obj.service_id +'_'+clctDt);
             bottomInfo.addClass('on');
-            bottomFrame.find('#evpBottomBodyTable').scrollTop(top);
+            bottomFrame.find('.bottomBody').scrollTop(32 * cnt);
+
+            const position = window.parent.getKakaoPosition(event_list[cnt].cur_lat, event_list[cnt].cur_lng);
+            draw.car.moveMarker(position);
+            setSigState(phase_list[cnt], draw);
 
-            cnt++;
         }, 500);
     }
 
+    function setSigState(phase, draw) {
+        phase.forEach((sigObj)=>{
+            const evpSig = draw.sig.get(sigObj.service_id + '_' + sigObj.seq_no);
+            if (evpSig === undefined) {
+                draw.sig.set(sigObj.service_id + '_' + sigObj.seq_no, window.parent.createSig(sigObj));
+            }
+            else {
+                if (!evpSig.a_ring && !evpSig.b_ring) {
+                    evpSig.createRing(sigObj);
+                }
+                else {
+                    const {a_head_lat, a_head_lng, a_mid_lat, a_mid_lng, a_end_lat, a_end_lng,
+                        b_head_lat, b_head_lng, b_mid_lat, b_mid_lng, b_end_lat, b_end_lng} = sigObj;
+                    const aPosition = [
+                        window.parent.getKakaoPosition(a_head_lat, a_head_lng),
+                        window.parent.getKakaoPosition(a_mid_lat, a_mid_lng),
+                        window.parent.getKakaoPosition(a_end_lat, a_end_lng),
+                    ]
+                    const bPosition = [
+                        window.parent.getKakaoPosition(b_head_lat, b_head_lng),
+                        window.parent.getKakaoPosition(b_mid_lat, b_mid_lng),
+                        window.parent.getKakaoPosition(b_end_lat, b_end_lng),
+                    ]
+
+                    evpSig.a_ring.setPath(aPosition);
+                    evpSig.b_ring.setPath(bPosition);
+                }
+                evpSig.setState(sigObj.state);
+            }
+        });
+    }
+
     function pauseSimulation() {
+        if (!$('#pause').hasClass('on')) {
+            return;
+        }
         clearInterval(interval);
         imageOnOff('pause', false);
         imageOnOff('play', true);
     }
 
     function stopSimulation() {
+        if (!$('#stop').hasClass('on')) {
+            return;
+        }
         clearInterval(interval);
         imageOnOff('stop', false);
         imageOnOff('pause', false);
         imageOnOff('play', true);
         cnt = 0;
         const serviceId = $('.history-table tr.on').attr('class').replace(/hs_| on/gi, '');
-        clearSimulator()
-        drawHistory(serviceId)
+        clearSimulator();
+        drawHistory(serviceId);
     }
 
     function clearSimulator() {