Browse Source

update 2024-11-11

junggilpark 11 months ago
parent
commit
2978d24696
41 changed files with 1672 additions and 298 deletions
  1. 1 1
      conf/tsi-sig-server.pid
  2. 3 0
      src/main/java/com/tsi/sig/server/TsiSigServerApplication.java
  3. 140 0
      src/main/java/com/tsi/sig/server/config/KafkaEvpConsumerConfig.java
  4. 0 1
      src/main/java/com/tsi/sig/server/controller/LoginController.java
  5. 8 3
      src/main/java/com/tsi/sig/server/controller/MainController.java
  6. 27 0
      src/main/java/com/tsi/sig/server/dto/EvpServiceEndDto.java
  7. 228 0
      src/main/java/com/tsi/sig/server/dto/EvpsData.java
  8. 49 0
      src/main/java/com/tsi/sig/server/dto/EvpsEventDto.java
  9. 27 0
      src/main/java/com/tsi/sig/server/dto/EvpsNetPingDto.java
  10. 32 0
      src/main/java/com/tsi/sig/server/dto/EvpsNodeDto.java
  11. 28 0
      src/main/java/com/tsi/sig/server/dto/EvpsPhaseDto.java
  12. 29 0
      src/main/java/com/tsi/sig/server/dto/EvpsRouteDto.java
  13. 81 0
      src/main/java/com/tsi/sig/server/dto/EvpsServiceDto.java
  14. 32 0
      src/main/java/com/tsi/sig/server/dto/EvpsServiceEndDto.java
  15. 33 0
      src/main/java/com/tsi/sig/server/dto/EvpsSignalDto.java
  16. 4 0
      src/main/java/com/tsi/sig/server/dto/KafkaEvpsData.java
  17. 45 0
      src/main/java/com/tsi/sig/server/dto/NodeInfo.java
  18. 73 0
      src/main/java/com/tsi/sig/server/dto/PhaseInfo.java
  19. 29 0
      src/main/java/com/tsi/sig/server/dto/RouteInfo.java
  20. 54 0
      src/main/java/com/tsi/sig/server/dto/SignalInfo.java
  21. 15 7
      src/main/java/com/tsi/sig/server/mapper/MainMapper.java
  22. 2 1
      src/main/java/com/tsi/sig/server/repository/ApplicationRepository.java
  23. 23 11
      src/main/java/com/tsi/sig/server/service/MainService.java
  24. 8 1
      src/main/java/com/tsi/sig/server/vo/EvpEventVo.java
  25. 1 1
      src/main/java/com/tsi/sig/server/vo/EvpRouteVo.java
  26. 6 6
      src/main/java/com/tsi/sig/server/vo/EvpServiceVo.java
  27. 1 1
      src/main/java/com/tsi/sig/server/vo/EvpSignalVo.java
  28. 106 0
      src/main/java/com/tsi/sig/server/websocket/EvpTopicHandler.java
  29. 219 0
      src/main/java/com/tsi/sig/server/websocket/kafka/EvpTopicConsumerThread.java
  30. 10 1
      src/main/resources/application.yml
  31. 62 0
      src/main/resources/mybatis/mapper/main.xml
  32. 9 2
      src/main/resources/static/css/main.css
  33. BIN
      src/main/resources/static/images/car.gif
  34. BIN
      src/main/resources/static/images/car2.gif
  35. BIN
      src/main/resources/static/images/evp_legend.png
  36. 151 223
      src/main/resources/static/js/map.js
  37. 27 0
      src/main/resources/static/js/signal.js
  38. 2 1
      src/main/resources/static/js/worker.js
  39. 4 3
      src/main/webapp/WEB-INF/jsp/bottomListFrame.jsp
  40. 1 1
      src/main/webapp/WEB-INF/jsp/main.jsp
  41. 102 34
      src/main/webapp/WEB-INF/jsp/treeListFrame.jsp

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

@@ -1 +1 @@
-36064
+9928

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

@@ -5,6 +5,7 @@ import com.tsi.sig.server.mapper.MainMapper;
 import com.tsi.sig.server.repository.ApplicationRepository;
 import com.tsi.sig.server.vo.CvibNodeVO;
 import com.tsi.sig.server.websocket.AllTopicCvimHandler;
+import com.tsi.sig.server.websocket.EvpTopicHandler;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.InitializingBean;
@@ -58,6 +59,7 @@ public class TsiSigServerApplication implements CommandLineRunner, ApplicationLi
         log.info("************************************************************************************");
 
         AllTopicCvimHandler allTopicCvimHandler = (AllTopicCvimHandler) AppUtils.getBean(AllTopicCvimHandler.class);
+        EvpTopicHandler evpTopicHandler = (EvpTopicHandler) AppUtils.getBean(EvpTopicHandler.class);
         //OneTopicCvimHandler oneTopicCvimHandler = (OneTopicCvimHandler) AppUtils.getBean(OneTopicCvimHandler.class);
         MainMapper mapper = (MainMapper)AppUtils.getBean(MainMapper.class);
         ApplicationRepository repo = (ApplicationRepository) AppUtils.getBean(ApplicationRepository.class);
@@ -67,6 +69,7 @@ public class TsiSigServerApplication implements CommandLineRunner, ApplicationLi
             repo.getNodeList().add(vo);
         }
         allTopicCvimHandler.start();
+        evpTopicHandler.start();
         //oneTopicCvimHandler.start();
     }
 

+ 140 - 0
src/main/java/com/tsi/sig/server/config/KafkaEvpConsumerConfig.java

@@ -0,0 +1,140 @@
+package com.tsi.sig.server.config;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.admin.AdminClient;
+import org.apache.kafka.clients.admin.AdminClientConfig;
+import org.apache.kafka.clients.admin.KafkaAdminClient;
+import org.apache.kafka.clients.admin.TopicDescription;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
+import org.springframework.kafka.listener.ContainerProperties;
+import org.springframework.kafka.listener.KafkaMessageListenerContainer;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.*;
+import java.util.concurrent.ExecutionException;
+
+@Slf4j
+@Data
+@Component
+@ConfigurationProperties(prefix = "application.kafka.evp.consumer")
+public class KafkaEvpConsumerConfig {
+    //기존
+    //private String groupId = "tsi-sig-server";
+    //변경
+    private String groupId;
+    private String bootstrapServers;
+    private String topic;
+    public List<Map<String, String>> props = new ArrayList<Map<String, String>>();
+
+    @PostConstruct
+    private void init() throws UnknownHostException {
+
+        log.info("[{}] --------------------", this.getClass().getSimpleName());
+        log.info("[{}]          groupId: {}", this.getClass().getSimpleName(), this.groupId);
+        log.info("[{}] bootstrapServers: {}", this.getClass().getSimpleName(), this.bootstrapServers);
+        log.info("[{}]            topic: {}", this.getClass().getSimpleName(), this.topic);
+        log.info("[{}]            props: {}", this.getClass().getSimpleName(), this.props.toArray());
+        String hostName;
+        try {
+            InetAddress localhost = InetAddress.getLocalHost();
+            hostName = localhost.getHostName();
+        } catch (UnknownHostException e) {
+            hostName = UUID.randomUUID().toString();
+        }
+        this.groupId = this.groupId + hostName;
+    }
+
+    public Properties getConsumerProperties() {
+        return getConsumerProperties(this.groupId);
+    }
+
+    public Properties getConsumerProperties(String groupId) {
+        Properties properties = new Properties();
+
+        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapServers);
+        properties.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
+        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
+        properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
+        properties .put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, org.apache.kafka.common.serialization.StringDeserializer.class);
+        properties .put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, org.apache.kafka.common.serialization.ByteBufferDeserializer.class);
+
+        for (Map<String, String> prop : this.props) {
+            for (Map.Entry<String, String> elem : prop.entrySet()) {
+                String key = elem.getKey();
+                String val = elem.getValue();
+                if (val != null) {
+                    if (val.equals("true") || val.equals("false")) {
+                        properties.put(key, val.equals("true"));
+                    } else {
+                        properties.put(key, val);
+                    }
+                }
+            }
+        }
+        return properties;
+    }
+
+    public Map<String, Object> getConsumerPropertiesMap() {
+        Map<String, Object> properties = new HashMap();
+        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapServers);
+        properties.put(ConsumerConfig.GROUP_ID_CONFIG, this.groupId);
+        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
+        properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
+        properties .put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, org.apache.kafka.common.serialization.StringDeserializer.class);
+        properties .put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, org.apache.kafka.common.serialization.ByteArrayDeserializer.class);
+
+        for (Map<String, String> prop : this.props) {
+            for (Map.Entry<String, String> elem : prop.entrySet()) {
+                String key = elem.getKey();
+                String val = elem.getValue();
+                if (val != null) {
+                    if (val.equals("true") || val.equals("false")) {
+                        properties.put(key, val.equals("true"));
+                    } else {
+                        properties.put(key, val);
+                    }
+                }
+            }
+        }
+        return properties;
+    }
+
+    private  <K,V> ConsumerFactory<K,V> consumerFactory(){
+        DefaultKafkaConsumerFactory<K,V> consumerFactory = new DefaultKafkaConsumerFactory<K,V>(getConsumerPropertiesMap());
+        return consumerFactory;
+    }
+
+    public <K,V> KafkaMessageListenerContainer<K, V> createKafkaConsumer(ContainerProperties containerProps) {
+
+        DefaultKafkaConsumerFactory<K, V> consumerFactory = new DefaultKafkaConsumerFactory(getConsumerPropertiesMap());
+        KafkaMessageListenerContainer<K, V> listenerContainer = new KafkaMessageListenerContainer((ConsumerFactory)consumerFactory, containerProps);
+        return listenerContainer;
+    }
+
+    public <T,V> ConcurrentMessageListenerContainer<T, V> createKafkaConsumerListener(ContainerProperties containerProps) {
+
+        DefaultKafkaConsumerFactory<T, V> consumerFactory = new DefaultKafkaConsumerFactory(getConsumerPropertiesMap());
+        ConcurrentMessageListenerContainer<T, V> listenerContainer = new ConcurrentMessageListenerContainer((ConsumerFactory)consumerFactory, containerProps);
+        return listenerContainer;
+    }
+
+    public int getPartitionCount(String topicName) throws ExecutionException, InterruptedException {
+        Properties properties = new Properties();
+        properties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapServers);
+
+        AdminClient adminClient = KafkaAdminClient.create(properties);
+
+        Map<String, TopicDescription> topics = adminClient.describeTopics(Collections.singletonList(topicName)).all().get();
+        log.error("{}", topics.get(topicName).partitions().size());
+        adminClient.close();
+        return topics.get(topicName).partitions().size();
+    }
+}

+ 0 - 1
src/main/java/com/tsi/sig/server/controller/LoginController.java

@@ -66,7 +66,6 @@ public class LoginController {
 
     @GetMapping({"/denied.do"})
     public String denied(HttpServletRequest req, @ModelAttribute("userVO") UserVO userVO) {
-        log.info("userVO : {}", userVO);
         if (userVO.getUserId() != null) {
             String useYN = userVO.getUseyn();
             req.setAttribute("userId", userVO.getUserId());

+ 8 - 3
src/main/java/com/tsi/sig/server/controller/MainController.java

@@ -23,7 +23,6 @@ import java.util.*;
 @Controller
 public class MainController {
     private final MainService mainService;
-
     public MainController(MainService mainService) {
         this.mainService = mainService;
     }
@@ -147,11 +146,17 @@ public class MainController {
         return mainService.getCvibNodeDetail(nodeId);
     }
 
-    @RequestMapping(value = "/getEvpServiceList.do", method = RequestMethod.POST)
+    @RequestMapping(value = "/getEvpsInfo.do", method = RequestMethod.POST)
     @ResponseBody
-    public List<EvpServiceVo> getEvpServiceList() throws Exception {
+    public List<EvpServiceVo> getEvpsInfoList() throws Exception{
         return mainService.getEvpServiceList();
     }
+
+//    @RequestMapping(value = "/getEvpServiceList.do", method = RequestMethod.POST)
+//    @ResponseBody
+//    public List<EvpServiceVo> getEvpServiceList() throws Exception {
+//        return mainService.getEvpServiceList();
+//    }
     @RequestMapping(value = "/getEvpRouteList.do", method = RequestMethod.POST)
     @ResponseBody
     public List<EvpRouteVo> getEvpRouteList(@RequestParam HashMap<String,Object> paramMap) throws Exception {

+ 27 - 0
src/main/java/com/tsi/sig/server/dto/EvpServiceEndDto.java

@@ -0,0 +1,27 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EvpServiceEndDto {
+
+    /**
+     * 긴급차량 서비스 ID
+     */
+    private String serviceId;
+    /**
+     * 수집시각
+     */
+    private String clctDt;
+    /**
+     * 서비스 상태 코드(1:진행중-서비스 진행중,2:정상종료-모든 교차로 제어 및 해제 완료,3:취소-아직 통과하지 않은 교차로 존재,4:센터강제종료-운영자가 서비스를 강제로 종료,5:비정상종료-서비스가 존재하지 않음,6:서비스시작실패-제어대상교차로가 없음,7:비정상종료-앱서버에 에러 발생,8:비정상종료-일정시간 앱에서 위치 및 속도 정보가 오지 않는 경우,9:자동종료-경로이탈,10:자동종료-경로진입 가능시간 초과,11:자동종료-정차가능시간 초과,12:취소-모든 교차로 제어및 해제 완료,13:실패-서비스 제어 요청 실패,14:실패-서비스 가능 교차로가 존재하지 않음,15:자동종료-위치정보 수신 가능 시간 초과)
+     */
+    private Integer reason;
+
+}

+ 228 - 0
src/main/java/com/tsi/sig/server/dto/EvpsData.java

@@ -0,0 +1,228 @@
+package com.tsi.sig.server.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.tsi.sig.server.vo.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@AllArgsConstructor
+@Slf4j
+public class EvpsData {
+    @JsonProperty("evps_event")
+    private EvpsEventDto evpsEvent;
+
+    @JsonProperty("evps_node")
+    private EvpsNodeDto evpsNode;
+
+    @JsonProperty("evps_service")
+    private EvpsServiceDto evpsService;
+
+    @JsonProperty("evps_signal")
+    private EvpsSignalDto evpsSignal;
+
+    private EvpServiceVo evpServiceVo;
+
+    private Map<String, PhaseInfo> evpPhaseMap;
+
+    private Map<Integer, RouteInfo> evpRouteMap;
+
+    public EvpsData() {
+        this.evpServiceVo = new EvpServiceVo();
+        this.evpServiceVo.setPhaseList(new ArrayList<>());
+        this.evpServiceVo.setEventList(new ArrayList<>());
+        this.evpServiceVo.setNodeList(new ArrayList<>());
+        this.evpServiceVo.setRouteList(new ArrayList<>());
+
+        this.evpPhaseMap = new HashMap<>();
+        this.evpRouteMap = new HashMap<>();
+    }
+
+    public void setServiceVo() {
+
+        if (this.evpsService != null) {
+            this.evpServiceVo.setServiceId(this.evpsService.getServiceId());
+            this.evpServiceVo.setClctDt(this.evpsService.getClctDt().substring(0, 18));
+            this.evpServiceVo.setServiceNm(this.evpsService.getServiceNm());
+            this.evpServiceVo.setEvNo(this.evpsService.getEvNo());
+            this.evpServiceVo.setCurLat(this.evpsService.getCurLat());
+            this.evpServiceVo.setCurLng(this.evpsService.getCurLng());
+            this.evpServiceVo.setArrLat(this.evpsService.getArrLat());
+            this.evpServiceVo.setArrLng(this.evpsService.getArrLng());
+            this.evpServiceVo.setArrTm(this.evpsService.getArrTm());
+            this.evpServiceVo.setVehLen(this.evpsService.getVehLen());
+            this.evpServiceVo.setOcrNo(this.evpsService.getOcrNo());
+            this.evpServiceVo.setOcrType(this.evpsService.getOcrType());
+            this.evpServiceVo.setServiceDist(this.evpsService.getServiceDist());
+            this.evpServiceVo.setStatusCd(this.evpsService.getStatusCd());
+
+            if (this.evpServiceVo.getRouteList() != null && this.evpsService.getRouteList().size() > 0) {
+                List<RouteInfo> routeInfos = this.evpsService.getRouteList();
+                String serviceId = this.evpsService.getServiceId();
+                for (RouteInfo routeInfo : routeInfos) {
+                    EvpRouteVo routeVo = new EvpRouteVo();
+                    Integer seqNo = routeInfo.getSeqNo();
+                    double lat = routeInfo.getLat();
+                    double lng = routeInfo.getLng();
+                    routeVo.setServiceId(serviceId);
+                    routeVo.setLat(lat);
+                    routeVo.setLng(lng);
+                    routeVo.setSeqNo(seqNo);
+                    evpServiceVo.getRouteList().add(routeVo);
+                    this.evpRouteMap.put(seqNo, routeInfo);
+                }
+            }
+        }
+    }
+
+    public void setNodeList() {
+        if (this.evpsNode == null || this.evpsNode.getNodeList().size() == 0) {
+            return;
+        }
+
+        List<NodeInfo> nodeInfos = this.evpsNode.getNodeList();
+        List<EvpNodeVo> nodeList = new ArrayList<>();
+        for (NodeInfo nodeInfo : nodeInfos) {
+            EvpNodeVo nodeVo = new EvpNodeVo();
+            String serviceId = this.evpsNode.getServiceId();
+            nodeVo.setServiceId(serviceId);
+            Integer seqNo = nodeInfo.getSeqNo();
+            Long nodeId = nodeInfo.getNodeId();
+            String nodeNm = nodeInfo.getNodeNm();
+            double lat = nodeInfo.getLat();
+            double lng = nodeInfo.getLng();
+            nodeVo.setNodeId(nodeId);
+            nodeVo.setNodeNm(nodeNm);
+            nodeVo.setLat(lat);
+            nodeVo.setLng(lng);
+            nodeVo.setSeqNo(seqNo);
+            nodeList.add(nodeVo);
+            if (nodeInfo.getPhaseList().size() > 0) {
+                List<PhaseInfo> phaseList = nodeInfo.getPhaseList();
+                for (PhaseInfo phase : phaseList) {
+                    String key = nodeInfo.getSeqNo() + "_" + nodeInfo.getNodeId() + "_" + phase.getRing() + "_" + phase.getPlanClass() + "_" + phase.getPhaseNo();
+                    this.evpPhaseMap.put(key, phase);
+                }
+            }
+        }
+        this.evpServiceVo.setNodeList(nodeList);
+    }
+
+    public void setEventList() {
+        if (this.evpServiceVo.getEventList().size() > 0) {
+            this.evpServiceVo.getEventList().clear();
+        }
+
+        if (this.evpsEvent != null) {
+            EvpEventVo vo = new EvpEventVo();
+            EvpsEventDto event = this.evpsEvent;
+            vo.setClctDt(event.getClctDt().substring(0, 18));
+            vo.setCurLat(event.getCurLat());
+            vo.setCurLng(event.getCurLng());
+            vo.setEvNo(event.getEvNo());
+            vo.setServiceId(event.getServiceId());
+            vo.setCurSpd(event.getCurSpd());
+            vo.setEventCd(event.getEventCd());
+            vo.setRemDist(event.getRemDist());
+            this.evpServiceVo.getEventList().add(vo);
+        }
+    }
+
+    public void setSignalList() {
+        if (this.evpsSignal == null || this.evpsSignal.getSignalList().size() == 0) {
+            return;
+        }
+
+        String serviceId = this.evpsSignal.getServiceId();
+        String clctDt =  this.evpsSignal.getClctDt().substring(0, 18);
+        List<SignalInfo> signalInfos = this.evpsSignal.getSignalList();
+        List<EvpSignalVo> phaseList = new ArrayList<>();
+        Map<Integer, EvpSignalVo> signalMap = new HashMap<>();
+        Map<Integer, SignalInfo> signalInfoMap = new HashMap<>();
+        for (EvpSignalVo vo : this.evpServiceVo.getPhaseList()) {
+            signalMap.put(vo.getSeqNo(), vo);
+        }
+
+        for (SignalInfo info : signalInfos) {
+            signalInfoMap.put(info.getSeqNo(), info);
+        }
+
+//        if (signalInfos.size() != this.evpRouteMap.size()) {
+//            for (Integer seq : new ArrayList<>(this.evpRouteMap.keySet())) {
+//                if (signalInfoMap.get(seq) != null || signalMap.get(seq) == null) {
+//                    continue;
+//                }
+//                signalInfos.add(signalMap.get(seq));
+//            }
+//        }
+        for (SignalInfo signalInfo : signalInfos) {
+            EvpSignalVo vo = new EvpSignalVo();
+            vo.setServiceId(serviceId);
+            vo.setClctDt(clctDt);
+            vo.setSeqNo(signalInfo.getSeqNo());
+            vo.setNodeId(signalInfo.getNodeId());
+            vo.setRemDist(signalInfo.getRemDist());
+            vo.setState(signalInfo.getState());
+            vo.setPlanClass(signalInfo.getPlanClass());
+            vo.setARingPhase(signalInfo.getARingPhase());
+            vo.setBRingPhase(signalInfo.getBRingPhase());
+
+            if (this.evpRouteMap.get(signalInfo.getSeqNo()) != null){
+                vo.setLat(this.evpRouteMap.get(signalInfo.getSeqNo()).getLat());
+                vo.setLng(this.evpRouteMap.get(signalInfo.getSeqNo()).getLng());
+            }
+
+            Integer aRing = signalInfo.getARingPhase();
+            Integer bRing = signalInfo.getBRingPhase();
+
+            if (signalInfo.getState() == 5) {
+                aRing = signalInfo.getHoldPhase();
+                bRing = signalInfo.getHoldPhase();
+            }
+
+            String aKey = signalInfo.getSeqNo() + "_" + signalInfo.getNodeId() + "_" + 1 + "_" + signalInfo.getPlanClass() + "_" + aRing;
+            String bKey = signalInfo.getSeqNo() + "_" + signalInfo.getNodeId() + "_" + 2 + "_" + signalInfo.getPlanClass() + "_" + bRing;
+
+            if (this.evpPhaseMap.get(aKey) != null) {
+                PhaseInfo aPhaseInfo = this.evpPhaseMap.get(aKey);
+                vo.setAFlowNo(aPhaseInfo.getFlowNo());
+                vo.setAHeadAngle(aPhaseInfo.getHeadAngle());
+                vo.setAEndAngle(aPhaseInfo.getEndAngle());
+                vo.setAHeadLat(aPhaseInfo.getHeadLat());
+                vo.setAHeadLng(aPhaseInfo.getHeadLng());
+                vo.setAMidLat(aPhaseInfo.getMidLat());
+                vo.setAMidLng(aPhaseInfo.getMidLng());
+                vo.setAEndLat(aPhaseInfo.getEndLat());
+                vo.setAEndLng(aPhaseInfo.getEndLng());
+            }
+            else {
+                log.error("SeqNo : {}, NodeId : {}, ringNo : 1, PlanClass : {}, ringPhase : {}", signalInfo.getSeqNo(), signalInfo.getNodeId(), signalInfo.getPlanClass(), aRing);
+            }
+
+            if (this.evpPhaseMap.get(bKey) != null) {
+                PhaseInfo bPhaseInfo = this.evpPhaseMap.get(bKey);
+                vo.setBFlowNo(bPhaseInfo.getFlowNo());
+                vo.setBHeadAngle(bPhaseInfo.getHeadAngle());
+                vo.setBEndAngle(bPhaseInfo.getEndAngle());
+                vo.setBHeadLat(bPhaseInfo.getHeadLat());
+                vo.setBHeadLng(bPhaseInfo.getHeadLng());
+                vo.setBMidLat(bPhaseInfo.getMidLat());
+                vo.setBMidLng(bPhaseInfo.getMidLng());
+                vo.setBEndLat(bPhaseInfo.getEndLat());
+                vo.setBEndLng(bPhaseInfo.getEndLng());
+            }
+            else {
+                log.error("SeqNo : {}, NodeId : {}, ringNo : 1, PlanClass : {}, ringPhase : {}", signalInfo.getSeqNo(), signalInfo.getNodeId(), signalInfo.getPlanClass(), bRing);
+            }
+            phaseList.add(vo);
+        }
+
+        this.evpServiceVo.setPhaseList(phaseList);
+    }
+}

+ 49 - 0
src/main/java/com/tsi/sig/server/dto/EvpsEventDto.java

@@ -0,0 +1,49 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 긴급차량 이벤트 정보
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EvpsEventDto implements KafkaEvpsData {
+
+    /**
+     * 긴급차량 서비스 ID
+     */
+    private String serviceId;
+    /**
+     * 수집시각
+     */
+    private String clctDt;
+    /**
+     * 긴급차량 번호(Not Used)
+     */
+    private String evNo;
+    /**
+     * 이벤트 코드(0:서비스시작, 1:차량위치, 2:서비스종료)
+     */
+    private Integer eventCd;
+    /**
+     * 현재위치 위도
+     */
+    private Double curLat;
+    /**
+     * 현재위치 경로
+     */
+    private Double curLng;
+    /**
+     * 현재 차량 속도
+     */
+    private Integer curSpd;
+    /**
+     * 목적지 남은거리(m)
+     */
+    private Integer remDist;
+}

+ 27 - 0
src/main/java/com/tsi/sig/server/dto/EvpsNetPingDto.java

@@ -0,0 +1,27 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 긴급차량 네트워크 연결 확인 정보
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EvpsNetPingDto implements KafkaEvpsData {
+
+    /**
+     * 긴급차량 서비스 ID
+     */
+    private String serviceId;
+
+    /**
+     * 일련번호
+     */
+    private Long sequenceNumber;
+
+}

+ 32 - 0
src/main/java/com/tsi/sig/server/dto/EvpsNodeDto.java

@@ -0,0 +1,32 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 긴급차량 서비스 교차로 정보
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EvpsNodeDto implements KafkaEvpsData {
+    /**
+     * 긴급차량 서비스 ID
+     */
+    private String serviceId;
+
+    /**
+     * 수집시각
+     */
+    private String clctDt;
+
+    @Builder.Default
+    private List<NodeInfo> nodeList = new ArrayList<>();
+
+}

+ 28 - 0
src/main/java/com/tsi/sig/server/dto/EvpsPhaseDto.java

@@ -0,0 +1,28 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 긴급차량 서비스 교차로 현시 정보
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EvpsPhaseDto implements KafkaEvpsData {
+
+    /**
+     * 긴급차량 서비스 ID
+     */
+    private String serviceId;
+
+    @Builder.Default
+    private List<PhaseInfo> phaseList = new ArrayList<>();
+
+}

+ 29 - 0
src/main/java/com/tsi/sig/server/dto/EvpsRouteDto.java

@@ -0,0 +1,29 @@
+package com.tsi.sig.server.dto;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 긴급차량 서비스 경로 정보
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EvpsRouteDto implements KafkaEvpsData {
+
+    /**
+     * 긴급차량 서비스 ID
+     */
+    private String serviceId;
+
+    @Builder.Default
+    private List<RouteInfo> routeList = new ArrayList<>();
+
+}

+ 81 - 0
src/main/java/com/tsi/sig/server/dto/EvpsServiceDto.java

@@ -0,0 +1,81 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 긴급차량 서비스 정보
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EvpsServiceDto implements KafkaEvpsData {
+
+    /**
+     * 긴급차량 서비스 ID
+     */
+    private String serviceId;
+    /**
+     * 수집시각
+     */
+    private String clctDt;
+    /**
+     * 긴급차량 번호
+     */
+    private String evNo;
+    /**
+     * 현재위치 위도
+     */
+    private Double curLat;
+    /**
+     * 현재위치 경로
+     */
+    private Double curLng;
+    /**
+     * 서비스명(목적지명)
+     */
+    private String serviceNm;
+    /**
+     * 도착위치 위도
+     */
+    private Double arrLat;
+    /**
+     * 도착위치 위도
+     */
+    private Double arrLng;
+    /**
+     * 예상도착시간(단위:초)
+     */
+    private Integer arrTm;
+    /**
+     * 차량길이(군집차량길이포함)
+     */
+    private Integer vehLen;
+    /**
+     * 재난번호
+     */
+    private String ocrNo;
+    /**
+     * 재난종별명
+     */
+    private String ocrType;
+    /**
+     * 거리(단위:m)
+     */
+    private Integer serviceDist;
+
+    /**
+     * 상태 코드
+     */
+    private Integer statusCd;
+
+    @Builder.Default
+    private List<RouteInfo> routeList = new ArrayList<>();
+
+}

+ 32 - 0
src/main/java/com/tsi/sig/server/dto/EvpsServiceEndDto.java

@@ -0,0 +1,32 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 긴급차량 서비스 종료 정보
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EvpsServiceEndDto implements KafkaEvpsData {
+
+    /**
+     * 긴급차량 서비스 ID
+     */
+    private String serviceId;
+
+    /**
+     * 수집시각
+     */
+    private String clctDt;
+
+    /**
+     * 서비스 상태 코드(1:진행중-서비스 진행중,2:정상종료-모든 교차로 제어 및 해제 완료,3:취소-아직 통과하지 않은 교차로 존재,4:센터강제종료-운영자가 서비스를 강제로 종료,5:비정상종료-서비스가 존재하지 않음,6:서비스시작실패-제어대상교차로가 없음,7:비정상종료-앱서버에 에러 발생,8:비정상종료-일정시간 앱에서 위치 및 속도 정보가 오지 않는 경우,9:자동종료-경로이탈,10:자동종료-경로진입 가능시간 초과,11:자동종료-정차가능시간 초과,12:취소-모든 교차로 제어및 해제 완료,13:실패-서비스 제어 요청 실패,14:실패-서비스 가능 교차로가 존재하지 않음,15:자동종료-위치정보 수신 가능 시간 초과)
+     */
+    private Integer reason;
+
+}

+ 33 - 0
src/main/java/com/tsi/sig/server/dto/EvpsSignalDto.java

@@ -0,0 +1,33 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 긴급차량 서비스 교차로 신호 정보
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EvpsSignalDto implements KafkaEvpsData {
+
+    /**
+     * 긴급차량 서비스 ID
+     */
+    private String serviceId;
+
+    /**
+     * 수집시각
+     */
+    private String clctDt;
+
+    @Builder.Default
+    private List<SignalInfo> signalList = new ArrayList<>();
+
+}

+ 4 - 0
src/main/java/com/tsi/sig/server/dto/KafkaEvpsData.java

@@ -0,0 +1,4 @@
+package com.tsi.sig.server.dto;
+
+public interface KafkaEvpsData {
+}

+ 45 - 0
src/main/java/com/tsi/sig/server/dto/NodeInfo.java

@@ -0,0 +1,45 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class NodeInfo {
+
+    /**
+     * 교차로 순서(1,...,N)
+     */
+    private Integer seqNo;
+
+    /**
+     * 교차로 ID
+     */
+    private Long nodeId;
+
+    /**
+     * 교차로명
+     */
+    private String nodeNm;
+
+    /**
+     * 위치 위도
+     */
+    private double lat;
+
+    /**
+     * 위치 경로
+     */
+    private double lng;
+
+    @Builder.Default
+    private List<PhaseInfo> phaseList = new ArrayList<>();
+}

+ 73 - 0
src/main/java/com/tsi/sig/server/dto/PhaseInfo.java

@@ -0,0 +1,73 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PhaseInfo {
+
+    /**
+     * 링번호(1:A링, 2:B링)
+     */
+    private Integer ring;
+
+    /**
+     * 맵 번호(0:일반제, 1~5:시차제, 6:전용맵)
+     */
+    private Integer planClass;
+
+    /**
+     * 현시번호(1~8)
+     */
+    private Integer phaseNo;
+
+    /**
+     * 이동류번호(1-16, 17, 18)
+     */
+    private Integer flowNo;
+
+    /**
+     * 시작점 위치 위도
+     */
+    private Double headLat;
+
+    /**
+     * 시작점 위치 경로
+     */
+    private Double headLng;
+
+    /**
+     * 중심점 위치 위도
+     */
+    private Double midLat;
+
+    /**
+     * 중심점 위치 경로
+     */
+    private Double midLng;
+
+    /**
+     * 끝점 위치 위도
+     */
+    private Double endLat;
+
+    /**
+     * 끝점 위치 경로
+     */
+    private Double endLng;
+
+    /**
+     * 시작점 각도
+     */
+    private Integer headAngle;
+
+    /**
+     * 종점 각도
+     */
+    private Integer endAngle;
+}

+ 29 - 0
src/main/java/com/tsi/sig/server/dto/RouteInfo.java

@@ -0,0 +1,29 @@
+package com.tsi.sig.server.dto;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class RouteInfo {
+
+     /**
+     * 경로 순서(1,...,N)
+     */
+    private Integer seqNo;
+
+    /**
+     * 위치 위도
+     */
+    private double lat;
+
+    /**
+     * 위치 경로
+     */
+    private double lng;
+}

+ 54 - 0
src/main/java/com/tsi/sig/server/dto/SignalInfo.java

@@ -0,0 +1,54 @@
+package com.tsi.sig.server.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SignalInfo {
+
+    /**
+     * 교차로 순서(1,...,N)
+     */
+    private Integer seqNo;
+
+    /**
+     * 교차로 ID
+     */
+    private Long nodeId;
+
+    /**
+     * 목적지 남은거리(m)
+     */
+    private Integer remDist;
+
+    /**
+     * 교차로운영상태(0:통신이상, 1:정상, 2:점멸, 3:소등, 4:수동진행, 5:현시유지)
+     */
+    private Integer state;
+
+    /**
+     * 현재 운영중인 맵 번호(0:일반제, 1~5:시차제, 6:전용맵)
+     */
+    private Integer planClass;
+
+    /**
+     * A링 현시번호(1-8)
+     */
+    private Integer aRingPhase;
+
+    /**
+     * B링 현시번호(1-8)
+     */
+    private Integer bRingPhase;
+
+    /**
+     * 유지현시번호(1-8)
+     */
+    private Integer holdPhase;
+}
+

+ 15 - 7
src/main/java/com/tsi/sig/server/mapper/MainMapper.java

@@ -1,5 +1,9 @@
 package com.tsi.sig.server.mapper;
 
+import com.tsi.sig.server.dto.EvpsServiceDto;
+import com.tsi.sig.server.dto.NodeInfo;
+import com.tsi.sig.server.dto.PhaseInfo;
+import com.tsi.sig.server.dto.RouteInfo;
 import com.tsi.sig.server.vo.*;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -11,14 +15,18 @@ import java.util.Map;
 public interface MainMapper {
 
     List<CvibNodeVO> getNodeList();
-    List<CvibNodeVO> getCvibNodeInfo(HashMap<String, Object> vo);
-    List<CvibNodeVO> getCvibMapNodeList(HashMap<String, Object> vo);
+    List<CvibNodeVO> getCvibNodeInfo(HashMap<String, Object> paramMap);
+    List<CvibNodeVO> getCvibMapNodeList(HashMap<String, Object> paramMap);
     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);
+    EvpsServiceDto getEvpServiceDto(Map<String, Object> paramMap);
+    List<EvpServiceVo> getEvpHistoryList(Map<String, Object> paramMap);
+    List<EvpRouteVo> getEvpRouteList(Map<String, Object> paramMap);
+    List<RouteInfo> getEvpRouteDtoList(Map<String, Object> paramMap);
+    List<EvpNodeVo> getEvpNodeList(Map<String, Object> paramMap);
+    List<NodeInfo> getEvpNodeDtoList(Map<String, Object> paramMap);
+    List<EvpPhaseVo> getEvpPhaseList(HashMap<String, Object> paramMap);
+    List<PhaseInfo> getEvpPhaseDtoList(Map<String, Object> paramMap);
+    List<EvpSignalVo> getEvpSignalList(Map<String, Object> paramMap);
     List<EvpEventVo> getEvpEventList(Map<String, Object> paramMap);
     List<EvpSignalVo> getEvpSignalCurrList(Map<String, Object> paramMap);
     List<EvpEventVo> getEvpEventCurrList(Map<String, Object> paramMap);

+ 2 - 1
src/main/java/com/tsi/sig/server/repository/ApplicationRepository.java

@@ -1,5 +1,6 @@
 package com.tsi.sig.server.repository;
 
+import com.tsi.sig.server.dto.EvpsData;
 import com.tsi.sig.server.vo.CvibNodeStatusVo;
 import com.tsi.sig.server.vo.CvibNodeVO;
 import com.tsi.sig.server.vo.CvibStatusVo;
@@ -17,7 +18,7 @@ import java.util.*;
 public class ApplicationRepository {
     private final List<CvibNodeVO> nodeList = new ArrayList<>();
     private final Map<String, CvibNodeStatusVo> nodeStatusMap = new HashMap<>();
-
+    private final Map<String, EvpsData> evpDataMap = new HashMap<>();
     public List<CvibStatusVo> getStatusList() throws ParseException {
         List<CvibStatusVo> list = new ArrayList<>();
 

+ 23 - 11
src/main/java/com/tsi/sig/server/service/MainService.java

@@ -1,6 +1,7 @@
 package com.tsi.sig.server.service;
 
 import com.tsi.sig.server.app.AppUtils;
+import com.tsi.sig.server.dto.EvpsData;
 import com.tsi.sig.server.mapper.MainMapper;
 import com.tsi.sig.server.repository.ApplicationRepository;
 import com.tsi.sig.server.vo.*;
@@ -18,6 +19,8 @@ import java.util.*;
 public class MainService {
 
     private final MainMapper mainMapper;
+    private final ApplicationRepository repo = (ApplicationRepository) AppUtils.getBean(ApplicationRepository.class);
+
 
     public MainService(MainMapper mainMapper) {
         this.mainMapper = mainMapper;
@@ -40,7 +43,6 @@ public class MainService {
 //        log.info("paramMap : {}",paramMap);
         long start = System.currentTimeMillis();
 
-        ApplicationRepository repo = (ApplicationRepository) AppUtils.getBean(ApplicationRepository.class);
         Map<String, CvibNodeStatusVo> repoMap = repo.getNodeStatusMap();
 
         List<CvibStatusVo> statusList = repo.getStatusList();
@@ -187,17 +189,27 @@ public class MainService {
         return result;
     }
 
+//    public List<EvpServiceVo> getEvpServiceList() {
+//        List<EvpServiceVo> result = this.mainMapper.getEvpServiceList();
+//        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));
+//            }
+//        }
+//        return result;
+//    }
+
     public List<EvpServiceVo> getEvpServiceList() {
-        List<EvpServiceVo> result = this.mainMapper.getEvpServiceList();
-        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));
-            }
+        Map<String, EvpsData> evpDataMap = repo.getEvpDataMap();
+        List<EvpsData> list = new ArrayList<>(evpDataMap.values());
+        List<EvpServiceVo> result = new ArrayList<>();
+        for (EvpsData data : list) {
+            result.add(data.getEvpServiceVo());
         }
         return result;
     }

+ 8 - 1
src/main/java/com/tsi/sig/server/vo/EvpEventVo.java

@@ -1,9 +1,13 @@
 package com.tsi.sig.server.vo;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class EvpEventVo {
 
     @JsonProperty("clct_dt")
@@ -12,6 +16,9 @@ public class EvpEventVo {
     @JsonProperty("service_id")
     private String serviceId;
 
+    @JsonProperty("ev_no")
+    private String evNo;
+
     @JsonProperty("event_cd")
     private Integer eventCd;
 
@@ -28,5 +35,5 @@ public class EvpEventVo {
     private Integer curSpd;
 
     @JsonProperty("rem_dist")
-    private Long remDist;
+    private Integer remDist;
 }

+ 1 - 1
src/main/java/com/tsi/sig/server/vo/EvpRouteVo.java

@@ -9,7 +9,7 @@ public class EvpRouteVo {
     private String serviceId;
 
     @JsonProperty("seq_no")
-    private Long seqNo;
+    private Integer seqNo;
 
     @JsonProperty("lat")
     private double lat;

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

@@ -26,16 +26,16 @@ public class EvpServiceVo {
     private String serviceNm;
 
     @JsonProperty("arr_lat")
-    private double arrLat;
+    private Double arrLat;
 
     @JsonProperty("arr_lng")
-    private double arrLng;
+    private Double arrLng;
 
     @JsonProperty("arr_tm")
-    private long arrTm;
+    private Integer arrTm;
 
     @JsonProperty("veh_len")
-    private long vehLen;
+    private Integer vehLen;
 
     @JsonProperty("ocr_no")
     private String ocrNo;
@@ -44,10 +44,10 @@ public class EvpServiceVo {
     private String ocrType;
 
     @JsonProperty("service_dist")
-    private long serviceDist;
+    private Integer serviceDist;
 
     @JsonProperty("status_cd")
-    private int statusCd;
+    private Integer statusCd;
 
     @JsonProperty("route_list")
     private List<EvpRouteVo> routeList;

+ 1 - 1
src/main/java/com/tsi/sig/server/vo/EvpSignalVo.java

@@ -21,7 +21,7 @@ public class EvpSignalVo {
     private String nodeNm;
 
     @JsonProperty("rem_dist")
-    private Long remDist;
+    private Integer remDist;
 
     @JsonProperty("state")
     private Integer state;

+ 106 - 0
src/main/java/com/tsi/sig/server/websocket/EvpTopicHandler.java

@@ -0,0 +1,106 @@
+package com.tsi.sig.server.websocket;
+
+import com.tsi.sig.server.config.KafkaEvpConsumerConfig;
+import com.tsi.sig.server.websocket.kafka.EvpTopicConsumerThread;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Controller
+@RequestMapping("/evpTopic.do")
+public class EvpTopicHandler extends TextWebSocketHandler {
+
+    private final KafkaEvpConsumerConfig kafkaEvpConsumerConfig;
+
+    private List<WebSocketSession> sessionList = new ArrayList<WebSocketSession>();
+    private EvpTopicConsumerThread consumerThread = null;
+    private Thread thread;
+
+    public EvpTopicHandler(KafkaEvpConsumerConfig kafkaEvpConsumerConfig) {
+        this.kafkaEvpConsumerConfig = kafkaEvpConsumerConfig;
+        this.consumerThread = new EvpTopicConsumerThread(this, this.kafkaEvpConsumerConfig);
+    }
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        // 클라이언트가 연결되었을때 실행
+        try {
+            this.sessionList.add(session);
+            this.consumerThread.addSession(session);
+        } catch (Exception e) {
+        }
+        log.info("EvpTopicCvimHandler, WebSocket [in]  RemoteAddress: " + session.getRemoteAddress() + ",  Uri" + session.getUri() + ", UUID" + session.getId());
+    }
+
+    //클라이언트가 웹소켓 서버로 메시지를 전송했을 때 실행
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+    }
+
+    //클라이언트 연결을 끊었을 때 실행
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
+        try {
+            this.consumerThread.removeSession(session);
+            this.sessionList.remove(session);
+        } catch (Exception e) {
+        }
+        log.info("EvpTopicCvimHandler, WebSocket [out]  RemoteAddress: " + session.getRemoteAddress() + ",  Uri: " + session.getUri() + ", UUID: " + session.getId());
+    }
+
+    public void sendMessage(WebSocketSession session, TextMessage message) {
+
+        if (session != null && session.isOpen()) {
+            synchronized (session) {
+                try {
+                    session.sendMessage(message);
+                } catch (Exception e) {
+                    log.error("EvpTopicCvimHandler, sendMessage: session: {}, {}", session, e.getMessage());
+                }
+            }
+        }
+    }
+
+    public void shutdown() {
+        if (!this.consumerThread.getStopFlag().get()) {
+            this.consumerThread.shutdown();
+        }
+    }
+
+    public void start() throws Exception {
+        if (this.consumerThread.getStopFlag().get()) {
+            log.info("EvpTopicCvimHandler, Start by stopFlag. {}", this.consumerThread);
+            this.thread = null;
+            this.consumerThread = null;
+            this.consumerThread = new EvpTopicConsumerThread(this, this.kafkaEvpConsumerConfig);
+            this.thread = new Thread(this.consumerThread);
+            this.thread.setUncaughtExceptionHandler(
+                    new Thread.UncaughtExceptionHandler() {
+                        @Override
+                        public void uncaughtException(Thread th, Throwable ex) {
+                            log.error("Uncaught exception: {}", ex.getMessage());
+                            shutdown();
+                        }
+                    }
+            );
+            this.thread.start();
+        }
+    }
+
+    public void stop() {
+        try {
+            if (!this.consumerThread.getStopFlag().get()) {
+                this.consumerThread.stop();
+            }
+        }
+        catch (Exception e) {
+        }
+    }
+}

+ 219 - 0
src/main/java/com/tsi/sig/server/websocket/kafka/EvpTopicConsumerThread.java

@@ -0,0 +1,219 @@
+package com.tsi.sig.server.websocket.kafka;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.tsi.sig.server.app.AppUtils;
+import com.tsi.sig.server.config.KafkaEvpConsumerConfig;
+import com.tsi.sig.server.dto.*;
+import com.tsi.sig.server.mapper.MainMapper;
+import com.tsi.sig.server.repository.ApplicationRepository;
+import com.tsi.sig.server.websocket.EvpTopicHandler;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.clients.consumer.KafkaConsumer;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.errors.WakeupException;
+import org.apache.kafka.common.utils.ByteBufferInputStream;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Getter
+@Setter
+public class EvpTopicConsumerThread implements Runnable {
+
+    private AtomicBoolean stopFlag = new AtomicBoolean(true);
+
+    private final Set<WebSocketSession> sessionSet = new HashSet<>();
+    private final EvpTopicHandler websocketHandler;
+    private final KafkaEvpConsumerConfig kafkaEvpConsumerConfig;
+    private final ApplicationRepository repo;
+    public static final String KAFKA_EVPS_SERVICE = "evps-service";
+    public static final String KAFKA_EVPS_NODE = "evps-node";
+    public static final String KAFKA_EVPS_SIGNAL = "evps-signal";
+    public static final String KAFKA_EVPS_EVENT = "evps-event";
+    public static final String KAFKA_EVPS_SERVICE_END = "evps-service-end";
+
+    private List<Map<String, Object>> signalData = new ArrayList<Map<String, Object>>();
+    private KafkaConsumer<String, ByteBuffer> consumer;
+    private String topicName;
+    private ObjectMapper mapper;
+    private MainMapper mainMapper;
+
+    public EvpTopicConsumerThread(EvpTopicHandler websocketHandler, KafkaEvpConsumerConfig kafkaConsumerConfig) {
+        this.repo = (ApplicationRepository) AppUtils.getBean(ApplicationRepository.class);
+        this.websocketHandler = websocketHandler;
+        this.kafkaEvpConsumerConfig = kafkaConsumerConfig;
+        this.consumer = new KafkaConsumer<>(this.kafkaEvpConsumerConfig.getConsumerProperties());
+        this.topicName = this.kafkaEvpConsumerConfig.getTopic();
+        this.mapper = new ObjectMapper();
+        this.mainMapper = (MainMapper)AppUtils.getBean(MainMapper.class);
+    }
+
+    private void SetDataMapALL(String key, ByteBuffer value, int valueSize){
+
+        InputStream is = new ByteBufferInputStream(value);
+
+        try {
+            if (KAFKA_EVPS_EVENT.equals(key)) {
+                EvpsEventDto event =  this.mapper.readValue(is, EvpsEventDto.class);
+                checkServiceInfo(event.getServiceId(), key);
+                if (this.repo.getEvpDataMap().get(event.getServiceId()) == null) return;
+                this.repo.getEvpDataMap().get(event.getServiceId()).setEvpsEvent(event);
+                this.repo.getEvpDataMap().get(event.getServiceId()).setEventList();
+            }
+            else if (KAFKA_EVPS_SIGNAL.equals(key)) {
+                EvpsSignalDto signal = mapper.readValue(is, EvpsSignalDto.class);
+                checkServiceInfo(signal.getServiceId(), key);
+                if (this.repo.getEvpDataMap().get(signal.getServiceId()) == null) return;
+                this.repo.getEvpDataMap().get(signal.getServiceId()).setEvpsSignal(signal);
+                this.repo.getEvpDataMap().get(signal.getServiceId()).setSignalList();;
+            }
+            else if (KAFKA_EVPS_NODE.equals(key)) {
+                EvpsNodeDto node = mapper.readValue(is, EvpsNodeDto.class);
+                checkServiceInfo(node.getServiceId(), key);
+                if (this.repo.getEvpDataMap().get(node.getServiceId()) == null) return;
+                this.repo.getEvpDataMap().get(node.getServiceId()).setEvpsNode(node);
+                this.repo.getEvpDataMap().get(node.getServiceId()).setNodeList();
+            }
+            else if (KAFKA_EVPS_SERVICE.equals(key)) {
+                EvpsServiceDto service = mapper.readValue(is, EvpsServiceDto.class);
+                log.info("===============EVP SERVICE START - ServiceId : {}, Time {}===================", service.getServiceId(), service.getClctDt());
+                this.repo.getEvpDataMap().put(service.getServiceId(), new EvpsData());
+                this.repo.getEvpDataMap().get(service.getServiceId()).setEvpsService(service);
+                this.repo.getEvpDataMap().get(service.getServiceId()).setServiceVo();
+            }
+            else if (KAFKA_EVPS_SERVICE_END.equals(key)) {
+                EvpsServiceEndDto serviceEnd = mapper.readValue(is, EvpsServiceEndDto.class);
+                if (serviceEnd.getReason() == 2) {
+                    if (this.repo.getEvpDataMap().get(serviceEnd.getServiceId()) != null) {
+                        this.repo.getEvpDataMap().remove(serviceEnd.getServiceId());
+                    }
+                    log.info("===============EVP SERVICE END - ServiceId : {}, Time {}===================", serviceEnd.getServiceId(), serviceEnd.getClctDt());
+                }
+            }
+            else {
+                log.error("Unknown Utic Evps Kafka Key: {}, {}", key, value);
+            }
+        }
+        catch (IOException e) {
+            e.getStackTrace();
+        }
+    }
+
+    /**
+     * 긴급차량 서비스 메모리 데이터가 있는지 체크
+     * @param serviceId 긴급차량 서비스 ID
+     * @param key Kafka key
+     */
+    public void checkServiceInfo(String serviceId, String key) {
+        Map<String, Object> param = new HashMap<>();
+        param.put("serviceId", serviceId);
+        if (this.repo.getEvpDataMap().get(serviceId) == null) { //메모리에 데이터가 없는 경우 데이터베이스에서 조회한다.
+            EvpsServiceDto dto = this.mainMapper.getEvpServiceDto(param);
+            if (dto == null) return; //서비스 조회가 안된다면 리턴
+
+            EvpsData evpsData = new EvpsData();
+            dto.setRouteList(this.mainMapper.getEvpRouteDtoList(param)); // 해당 서비스의 ROUTE 정보 조회
+            evpsData.setEvpsService(dto);
+            evpsData.setServiceVo();
+            this.repo.getEvpDataMap().put(serviceId, evpsData);
+        }
+
+        //NODE LIST 데이터가 아닌 경우 NODE LIST 조회해서 메모리에 추가 해준다.
+        if (KAFKA_EVPS_NODE.equals(key) || this.repo.getEvpDataMap().get(serviceId).getEvpsNode() != null) return;
+
+        List<NodeInfo> list = this.mainMapper.getEvpNodeDtoList(param);
+        if (list == null || list.size() == 0) return;
+
+        for (NodeInfo node : list) {
+            param.put("nodeId", node.getNodeId());
+            param.put("seqNo", node.getSeqNo());
+            node.setPhaseList(this.mainMapper.getEvpPhaseDtoList(param));
+        }
+
+        this.repo.getEvpDataMap().get(serviceId).getEvpsNode().setNodeList(list);
+    }
+
+    public List<String> formatPartitions(Collection<TopicPartition> partitions) {
+        return partitions.stream().map(topicPartition ->
+                        String.format("\ntopic: %s, partition: %s", topicPartition.topic(), topicPartition.partition()))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void run() {
+
+        log.info("EvpTopicConsumerThread, Start consumer: {}", this.topicName);
+
+        this.stopFlag.set(false);
+
+        try {
+            this.consumer.subscribe(Collections.singletonList(this.topicName),
+                    new ConsumerRebalanceListener() {
+                        @Override
+                        public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
+                            log.info("EvpTopicConsumerThread, onPartitionsRevoked - consumerName: {}, partitions: {}", topicName, formatPartitions(partitions));
+                        }
+
+                        @Override
+                        public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
+                            log.info("EvpTopicConsumerThread, onPartitionsAssigned - consumerName: {}, partitions: {}", topicName, formatPartitions(partitions));
+                            consumer.seekToEnd(partitions);
+                        }
+                    });
+            while (!this.stopFlag.get() && (!Thread.currentThread().isInterrupted())) {
+
+                ConsumerRecords<String, ByteBuffer> records = this.consumer.poll(Duration.ofMillis(100));
+
+                for (ConsumerRecord<String, ByteBuffer> record : records) {
+                    SetDataMapALL(record.key(), record.value(), record.serializedValueSize());
+                }
+            }
+            log.info("EvpTopicConsumerThread, ConsumerThread: {}, stopped.", this.topicName);
+        }
+        catch(WakeupException e) {
+            log.error("EvpTopicConsumerThread, Consumer WakeupException: {}, {}", this.topicName, e);
+            stop();
+        }
+        finally {
+            //this.consumer.commitSync();
+            stop();
+            try {
+                this.consumer.close();
+            } catch(Exception e) {}
+        }
+    }
+
+    public void addSession(WebSocketSession session) {
+        synchronized (this.sessionSet) {
+            this.sessionSet.add(session);
+        }
+    }
+
+    public void removeSession(WebSocketSession session) {
+        log.info("EvpTopicConsumerThread, remove sessions: {}", this.sessionSet.toString());
+        synchronized (this.sessionSet) {
+            this.sessionSet.remove(session);
+        }
+    }
+
+    public void stop() {
+        this.stopFlag.set(true);
+    }
+
+    public void shutdown() {
+        log.info("EvpTopicConsumerThread, shutdown wakeup: {}", this.getClass().getSimpleName());
+        this.consumer.wakeup();
+    }
+}

+ 10 - 1
src/main/resources/application.yml

@@ -89,6 +89,11 @@ application:
       #bootstrap-server 변경
       bootstrap-servers: 172.24.0.30:9092,172.24.0.31:9093,172.24.0.32:9094
       #bootstrap-servers: 61.82.138.91:19091,61.82.138.91:19092,61.82.138.91:19093
+    evp:
+      consumer:
+        bootstrap-servers: 172.24.0.30:9092,172.24.0.31:9093,172.24.0.32:9094
+        groupId: tsi-evp-was
+        topic: utic-evps
 
 ---
 spring:
@@ -107,4 +112,8 @@ application:
     consumer:
       bootstrap-servers: 61.82.138.91:19091,61.82.138.91:19092,61.82.138.91:19093
       #bootstrap-servers: 123.142.27.53:9092,123.142.27.53:9093,123.142.27.53:9094
-
+    evp:
+      consumer:
+        bootstrap-servers: 61.82.138.91:19092
+        groupId: tsi-evp-was
+        topic: utic-evps-sim

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

@@ -54,6 +54,26 @@
 		ORDER BY CLCT_DT
 	</select>
 
+	<select id="getEvpServiceDto" resultType="com.tsi.sig.server.dto.EvpsServiceDto">
+		SELECT SERVICE_ID AS serviceId,
+			   DATE_FORMAT(clct_dt, '%Y-%m-%d %H:%i:%s') clctDt,
+			   EV_NO AS evNo,
+			   CUR_LAT AS curLat,
+			   CUR_LNG AS curLng,
+			   SERVICE_NM AS serviceNm,
+			   ARR_LAT AS arrLat,
+			   ARR_LNG AS arrLng,
+			   ARR_TM AS arrTm,
+			   VEH_LEN AS vehLen,
+			   OCR_NO AS ocrNo,
+			   OCR_TYPE AS ocrType,
+			   SERVICE_DIST AS serviceDist,
+			   STATUS_CD AS statusCd
+		FROM tb_evp_service
+		WHERE STATUS_CD = 1
+		  AND SERVICE_ID = #{serviceId}
+	</select>
+
 	<select id="getEvpHistoryList" parameterType="java.util.HashMap" resultType="com.tsi.sig.server.vo.EvpServiceVo">
 		SELECT SERVICE_ID,
 			   DATE_FORMAT(clct_dt, '%Y-%m-%d %H:%i:%s') CLCT_DT,
@@ -91,6 +111,17 @@
 		  AND LNG BETWEEN 125.06666667 AND 131.87222222
 	</select>
 
+	<select id="getEvpRouteDtoList" parameterType="java.util.HashMap" resultType="com.tsi.sig.server.dto.RouteInfo">
+		SELECT SERVICE_ID,
+			   SEQ_NO,
+			   LAT,
+			   LNG
+		FROM tb_evp_route
+		WHERE SERVICE_ID = #{serviceId}
+		  AND LAT BETWEEN 33.10000000 AND 38.45000000
+		  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,
@@ -104,6 +135,18 @@
 		  AND LNG BETWEEN 125.06666667 AND 131.87222222
 	</select>
 
+	<select id="getEvpNodeDtoList" parameterType="java.util.HashMap" resultType="com.tsi.sig.server.dto.NodeInfo">
+		SELECT SEQ_NO AS seqNo,
+			   NODE_ID AS nodeId,
+			   NODE_NM AS nodeNm,
+			   LAT AS lat,
+			   LNG AS 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,
@@ -123,6 +166,25 @@
 		FROM TB_EVP_PHASE
 	</select>
 
+	<select id="getEvpPhaseDtoList" parameterType="java.util.HashMap" resultType="com.tsi.sig.server.dto.PhaseInfo">
+		SELECT RING AS ring,
+			   PLAN_CLASS AS planClass,
+			   PHASE_NO AS phaseNo,
+			   FLOW_NO AS flowNo,
+			   HEAD_LAT AS headLat,
+			   HEAD_LNG AS headLng,
+			   MID_LAT AS midLat,
+			   MID_LNG AS midLng,
+			   END_LAT AS endLat,
+			   END_LNG AS endLng,
+			   HEAD_ANGLE AS headAngle,
+			   END_ANGLE AS endAngle
+		FROM TB_EVP_PHASE
+		WHERE SERVICE_ID = #{serviceId}
+		  AND NODE_ID = #{nodeId}
+		  AND SEQ_NO = #{seqNo}
+	</select>
+
 	<select id="getEvpSignalList" parameterType="java.util.HashMap" resultType="com.tsi.sig.server.vo.EvpSignalVo">
 		SELECT
 			A.seq_no,

+ 9 - 2
src/main/resources/static/css/main.css

@@ -1253,13 +1253,16 @@ div.popState dl.jaywork dd strong.rblue {color:#607cd4; text-align:right;}
 }
 .leftMenu .title {
     height: 25px;
-    background-color: #3c3c3c;
-    color: #b2b2b2;
+    /*background-color: #3c3c3c;*/
+    background-color: #0065d1;
+    /*color: #b2b2b2;*/
+    color: white;
     display: flex;
     align-items: center;
     box-sizing: border-box;
     padding-left: 10px;
     border-bottom: 1px solid #595959;
+    font-weight: bold;
 }
 .leftMenu .historyBox > div:nth-child(2) {
     display: flex;
@@ -3383,6 +3386,7 @@ cursor: pointer;border: 1px solid #ebebeb;border-bottom-color: #e2e2e2;border-ra
 /*    overflow-y: scroll;*/
 /*    border-bottom: 1px solid #595959*/
 /*}*/
+.leftMenu #intTree::-webkit-scrollbar,
 .bottomMenu .bottomBody::-webkit-scrollbar,
 #historyList::-webkit-scrollbar,
 .bottomInfo .bottomInfoBottom .bottomInfoBody::-webkit-scrollbar {
@@ -3392,6 +3396,7 @@ cursor: pointer;border: 1px solid #ebebeb;border-bottom-color: #e2e2e2;border-ra
     border-radius: 5px;
 }
 
+.leftMenu #intTree::-webkit-scrollbar-thumb,
 .bottomMenu .bottomBody::-webkit-scrollbar-thumb,
 #historyList::-webkit-scrollbar-thumb,
 .bottomInfo .bottomInfoBottom .bottomInfoBody::-webkit-scrollbar-thumb {
@@ -3608,6 +3613,8 @@ img[src="images/logout.png"]{
 #map img[src="/images/CvibLight_9_2.gif"],
 #map img[src="/images/CvibLight_9_3.gif"],
 #map img[src="/images/CvibLight_9_4.gif"],
+#map img[src="/images/car.gif"],
+#map img[src="/images/car2.gif"],
 #map img[src="http://t1.kakaocdn.net/localimg/localimages/07/mapapidoc/roadview_wk.png"]
 /*#map .mapToggle img,*/
 /*#map img[src="images/arrow_left.png"],*/

BIN
src/main/resources/static/images/car.gif


BIN
src/main/resources/static/images/car2.gif


BIN
src/main/resources/static/images/evp_legend.png


+ 151 - 223
src/main/resources/static/js/map.js

@@ -39,9 +39,36 @@ const stateMap = new Map();
 stateMap.set(0, '#FF0000');
 stateMap.set(1, '#00FF00');
 stateMap.set(2, '#FFFF00');
-stateMap.set(3, '#808080');
+stateMap.set(3, '#E5DEDE');
 stateMap.set(4, '#CC6600');
-stateMap.set(5, '#9933FF');
+stateMap.set(5, '#F006D7');
+
+const strokeMap = new Map();
+strokeMap.set(1, 100);
+strokeMap.set(2, 40);
+strokeMap.set(3, 20);
+strokeMap.set(4, 10);
+strokeMap.set(5, 10);
+strokeMap.set(6, 10);
+strokeMap.set(7, 10);
+strokeMap.set(8, 10);
+strokeMap.set(9, 10);
+strokeMap.set(10, 10);
+strokeMap.set(11, 10);
+
+const arrowMap = new Map();
+arrowMap.set(1, 20);
+arrowMap.set(2, 10);
+arrowMap.set(3, 5);
+arrowMap.set(4, 5);
+arrowMap.set(5, 2);
+arrowMap.set(6, 1);
+arrowMap.set(7, 1);
+arrowMap.set(8, 1);
+arrowMap.set(9, 1);
+arrowMap.set(10, 1);
+arrowMap.set(11, 1);
+
 var _isClick = null;
 let _evpData = [];
 
@@ -153,7 +180,8 @@ function init() {
     }
 
     function drawEvp(serviceId) {
-
+        const bottom =   $('#iframeBottomList').contents().find('#evpBottomInfo');
+        bottom.empty();
         if (serviceId) {
            const $iframe = $('#iframeTreeList');
            const iframe = $iframe.contents();
@@ -230,55 +258,8 @@ function drawHistory(data) {
 }
 
 function createSig(data) {
-    return new EmergencySig(data);
-}
-
-function setEmergencyCurr(car, sigMap, road, arrive, dataMap) {
-    const sigArr   = dataMap.get('sig');
-    const eventObj = dataMap.get('event')
-    const eventCd  = eventObj.event_cd;
-    if (eventCd === 1) {
-        const {cur_lat, cur_lng} = eventObj;
-        car.moveMarker(getKakaoPosition(cur_lat, cur_lng));
-    }
-    else if (eventCd === 2) {
-        car.hide();
-        sigArr.forEach((obj)=>{obj.hide()});
-        arrive.hide();
-        road.hide();
-        return;
-    }
-
-    if (sigArr && sigArr.length > 0) {
-        for (let sig of sigArr) {
-            const sigMarker = sigMap.get(sig.service_id + '_' + sig.seq_no);
-            if (sigMarker) {
-                if (!sigMarker.a_ring && !sigMarker.b_ring) {
-                    sigMarker.createRing(sig);
-                }
-                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} = sig;
-                        const aPosition = [
-                            getKakaoPosition(a_head_lat, a_head_lng),
-                            getKakaoPosition(a_mid_lat, a_mid_lng),
-                            getKakaoPosition(a_end_lat, a_end_lng),
-                        ]
-                        const bPosition = [
-                            getKakaoPosition(b_head_lat, b_head_lng),
-                            getKakaoPosition(b_mid_lat, b_mid_lng),
-                            getKakaoPosition(b_end_lat, b_end_lng),
-                        ]
-
-                        // console.log(aPosition, bPosition);
-                        sigMarker.a_ring.setPath(aPosition);
-                        sigMarker.b_ring.setPath(bPosition);
-                }
-                sigMarker.setState(sig.state);
-            }
-        }
-    }
-
+    // return new EmergencySig(data);
+    return new EmergencyCircle(data);
 }
 
 function drawEmergencyRoad(data) {
@@ -288,30 +269,9 @@ function drawEmergencyRoad(data) {
             if (evpObj) {
                 evpObj.car.moveMarker(getKakaoPosition(data.event_list[0].cur_lat, data.event_list[0].cur_lng));
                 data.phase_list.forEach((obj)=>{
-                    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, state} = obj;
                     const sig = evpObj.sig.get(obj.service_id + '_' + obj.seq_no);
                     if (sig) {
-                        sig.setState(state);
-                        if (sig.a_ring) {
-                            const a_head = getKakaoPosition(a_head_lat, a_head_lng);
-                            const a_mid = getKakaoPosition(a_mid_lat, a_mid_lng);
-                            const a_end = getKakaoPosition(a_end_lat, a_end_lng);
-                            sig.a_ring.setPath([a_head, a_mid, a_end]);
-                        }
-
-                        if (sig.b_ring) {
-                            const b_head = getKakaoPosition(b_head_lat, b_head_lng);
-                            const b_mid = getKakaoPosition(b_mid_lat, b_mid_lng);
-                            const b_end = getKakaoPosition(b_end_lat, b_end_lng);
-                            sig.b_ring.setPath([b_head, b_mid, b_end]);
-                        }
-                        if (sig.a_ring === null && sig.b_ring === null) {
-                            sig.createRing(data);
-                        }
-                    }
-                    else {
-                        evpObj.sig.set(obj.service_id + '_' + obj.seq_no, new EmergencySig(obj));
+                        sig.createRing(obj);
                     }
                 });
             }
@@ -340,6 +300,7 @@ function drawEmergencyRoad(data) {
     }
 }
 
+
 function clearEmgMarker() {
     if (_EmergencyMap.size > 0) {
         _EmergencyMap.forEach((obj)=>{
@@ -809,7 +770,6 @@ function getDrawRoad() {
 
                 // 마우스로 클릭한 위치입니다
                 var clickPosition = mouseEvent.latLng;
-                console.log(clickPosition);
                 // 지도 클릭이벤트가 발생했는데 선을 그리고있는 상태가 아니면
                 if (!drawingFlag) {
                     //거리재기초기화
@@ -1529,7 +1489,7 @@ class EmergencyObj{
     constructor({ arr_lat, arr_lng, arr_tm, clct_dt,
                     cur_lat, cur_lng, ev_no, ocr_no, ocr_type,
                     phase_list, route_list, service_dist,
-                    service_id, service_nm, status_cd, veh_len, event_list }) {
+                    service_id, service_nm, status_cd, veh_len, event_list, node_list }) {
         this.arr_lat = arr_lat;
         this.arr_lng = arr_lng;
         this.arr_tm = arr_tm;
@@ -1551,19 +1511,28 @@ class EmergencyObj{
         this.car = null;
         this.arrive = null;
         this.event_list = event_list;
+        this.node_list = node_list;
         this.init();
     }
 
     init() {
-        this.road = new EmergencyRoadObj(this.route_list);
         this.setBound();
+        this.road = new EmergencyRoadObj(this.route_list);
+        if (this.node_list && this.node_list.length) {
+            this.node_list.forEach((obj)=>{
+                this.sig.set(obj.service_id + '_' + obj.seq_no , new EmergencyCircle(obj));
+            })
+        }
+
         if (this.phase_list && this.phase_list.length) {
             this.phase_list.forEach((obj)=>{
-                const sig = new EmergencySig(obj);
-                this.sig.set(obj.service_id + '_' + obj.seq_no, sig);
+                const circle = this.sig.get(obj.service_id + '_' + obj.seq_no);
+                if (circle) {
+                    circle.createRing(obj);
+                }
             })
         }
-        this.car = new EmergencyMarker(getKakaoPosition(this.event_list[0].cur_lat, this.event_list[0].cur_lng), '/images/car.svg');
+        this.car = new EmergencyMarker(getKakaoPosition(this.event_list[0].cur_lat, this.event_list[0].cur_lng), '/images/car.gif');
         this.arrive = new EmergencyMarker(getKakaoPosition(this.arr_lat, this.arr_lng), '/images/evp_arr.svg');
     }
 
@@ -1586,11 +1555,28 @@ class EmergencyObj{
         if (this.arrive) {
             this.arrive.hide();
         }
+
+        const tree = $('#iframeTreeList').contents().find('#evp-table tr.on');
+        if (tree.length) {
+            const bottom = $('#iframeBottomList').contents().find('#evpBottomInfo');
+            bottom.empty();
+        }
     }
 
     setBound() {
         const bounds = new kakao.maps.LatLngBounds();
-        bounds.extend(getKakaoPosition(this.cur_lat, this.cur_lng));
+        if (this.cur_lng >= 125.06666667 &&
+            this.cur_lng <= 131.87222222 &&
+            this.cur_lat >= 33.10000000  &&
+            this.cur_lat <= 38.45000000) {
+            bounds.extend(getKakaoPosition(this.cur_lat, this.cur_lng));
+        }
+        else {
+            if (this.event_list[0]) {
+                const {cur_lat, cur_lng} = this.event_list[0];
+                bounds.extend(getKakaoPosition(cur_lat, cur_lng));
+            }
+        }
         bounds.extend(getKakaoPosition(this.arr_lat, this.arr_lng));
         map.setBounds(bounds);
     }
@@ -1628,7 +1614,7 @@ class Ring{
             }
             // 선이 그려지고 있을 때 마우스 움직임에 따라 선이 그려질 위치를 표시할 선을 생성합니다
             this.polyLine = new kakao.maps.Polyline({
-                strokeWeight: 5, // 선의 두께입니다
+                strokeWeight: arrowMap.get(_Level), // 선의 두께입니다
                 path: [headPosition, midPosition, endPosition],
                 strokeColor: stateColor, // 선의 색깔입니다
                 strokeOpacity: 1, // 선의 불투명도입니다 0에서 1 사이값이며 0에 가까울수록 투명합니다
@@ -1662,6 +1648,10 @@ class Ring{
         }
     }
 
+    setLineWidth() {
+        this.setOptions({strokeWeight :  arrowMap.get(_Level)});
+    }
+
     setState(color) {
         if (this.polyLine) {
             this.polyLine.setOptions({
@@ -1671,79 +1661,89 @@ class Ring{
     }
 }
 
-const sigMap = new Map();
 
-class EmergencySig{
-    constructor({clct_dt, service_id, node_id, node_nm, lat, lng, rem_dist, state, plan_class, a_ring_phase, b_ring_phase,
-                    hold_phase, a_flow_no, a_head_angle, a_end_angle, a_head_lat, a_head_lng, a_mid_lat, a_mid_lng, a_end_lat,
-                    a_end_lng, b_flow_no, b_head_angle, b_end_angle, b_head_lat, b_head_lng, b_mid_lat, b_mid_lng, b_end_lat,
-                    b_end_lng, seq_no}) {
-        this.seq_no = seq_no;
-        this.clct_dt = clct_dt;
+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.rem_dist = rem_dist;
-        this.state = state;
-        this.plan_class = plan_class;
-        this.a_ring_phase = a_ring_phase;
-        this.b_ring_phase = b_ring_phase;
-        this.hold_phase = hold_phase;
+        this.circle = null;
         this.a_ring = null;
         this.b_ring = null;
-        this.circle = null;
-        this.a_flow_no = a_flow_no;
-        this.b_flow_no = b_flow_no;
-        this.a_head_angle = a_head_angle;
-        this.b_head_angle = b_head_angle;
-        this.a_end_angle = a_end_angle;
-        this.b_end_angle = b_end_angle;
-        this.a_head_lat = a_head_lat;
-        this.a_head_lng = a_head_lng;
-        this.b_head_lat = b_head_lat;
-        this.b_head_lng = b_head_lng;
-        this.a_mid_lat = a_mid_lat;
-        this.a_mid_lng = a_mid_lng;
-        this.b_mid_lat = b_mid_lat;
-        this.b_mid_lng = b_mid_lng;
-        this.a_end_lat = a_end_lat;
-        this.a_end_lng = a_end_lng;
-        this.b_end_lat = b_end_lat;
-        this.b_end_lng = b_end_lng;
         this.init();
     }
 
     init() {
-        let stateColor = '#FF0000';
-        if (!isNaN(Number(this.state))) {
-            stateColor = stateMap.get(Number(this.state));
-        }
+        let stateColor = '#00FF00';
         const position = getKakaoPosition(this.lat, this.lng);
+        const nodeNm = this.node_nm;
+        this.circle = new kakao.maps.Circle({
+            center : position,  // 원의 중심좌표 입니다
+            radius: 60, // 미터 단위의 원의 반지름입니다
+            strokeWeight: arrowMap.get(_Level), // 선의 두께입니다
+            strokeColor: stateColor, // 선의 색깔입니다
+            strokeOpacity: 1, // 선의 불투명도 입니다 1에서 0 사이의 값이며 0에 가까울수록 투명합니다
+            strokeStyle: 'solid', // 선의 스타일 입니다
+            fillColor: stateColor, // 채우기 색깔입니다
+            fillOpacity: 0.3, // 채우기 불투명도 입니다
+            zIndex: 2,
+            name : nodeNm,
+        });
+        this.show();
+    }
 
-        if (!sigMap.get(this.service_id + '_' + this.seq_no)) {
-            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 : '신호현시',
-            });
-            sigMap.set(this.lat + '_' + this.lng, this.circle);
+    show() {
+        if (this.circle) {
+            this.circle.setMap(map);
         }
 
-        this.createRing(this);
+        if (this.a_ring) {
+            this.a_ring.show();
+        }
 
-        this.show();
+        if (this.b_ring) {
+            this.b_ring.show();
+        }
+    }
+
+    hide() {
+        if (this.circle) {
+            this.circle.setMap(null);
+        }
+
+        this.deleteRing();
+    }
+
+    setState(state){
+        const stateColor = this.getStateColor(state);
+        if (this.circle) {
+            this.circle.setOptions({
+                strokeColor: stateColor,
+                fillColor : stateColor,
+            });
+        }
+    }
+
+    getStateColor(state) {
+        let stateColor = '#FF0000';
+        if (!isNaN(Number(state))) {
+            stateColor = stateMap.get(Number(state));
+        }
+        return stateColor;
     }
 
     createRing(data) {
+        if (this.a_ring) {
+            this.a_ring.hide();
+            this.a_ring = null;
+        }
+        if (this.b_ring) {
+            this.b_ring.hide();
+            this.b_ring = null;
+        }
         const stateArr = [1,2,4,5];
         if (stateArr.includes(data.state)) {
             this.a_ring = new Ring({
@@ -1782,108 +1782,30 @@ class EmergencySig{
 
             this.a_ring.show();
             this.b_ring.show();
+            this.setState(data.state);
         }
     }
 
-    getStateColor(state) {
-        let stateColor = '#FF0000';
-        if (!isNaN(Number(state))) {
-            stateColor = stateMap.get(Number(state));
-        }
-        return stateColor;
-    }
-
-    setState(state) {
-        const stateColor = this.getStateColor(state);
+    setLineWidth() {
         if (this.circle) {
-            this.circle.setOptions({
-                strokeColor: stateColor,
-                fillColor : stateColor,
-            });
+            this.circle.setOptions({strokeWeight :  arrowMap.get(_Level)})
         }
-
         if (this.a_ring) {
-            this.a_ring.setState(stateColor);
+            this.a_ring.setLineWidth()
         }
 
         if (this.b_ring) {
-            this.b_ring.setState(stateColor);
+            this.b_ring.setLineWidth()
         }
     }
-    show() {
-        if (this.circle) {
-            this.circle.setMap(map);
-        }
-
-        if (this.a_ring) {
-            this.a_ring.show();
-        }
-
-        if (this.b_ring) {
-            this.b_ring.show();
-        }
-    }
-
-    hide() {
-        if (this.circle) {
-            this.circle.setMap(null);
-        }
-
+    deleteRing() {
         if (this.a_ring) {
             this.a_ring.hide();
+            this.a_ring = null;
         }
         if (this.b_ring) {
             this.b_ring.hide();
-        }
-
-        if (sigMap && sigMap.size) {
-            sigMap.clear();
-        }
-    }
-
-    setCenter() {
-        map.setCenter(getKakaoPosition(this.lng, this.lat));
-    }
-}
-
-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);
+            this.b_ring = null;
         }
     }
 }
@@ -1904,9 +1826,10 @@ class EmergencyRoadObj{
                 this.kakaoPathArr.push(getKakaoPosition(obj.lat, obj.lng));
             });
 
+            let strokeWidth = strokeMap.get(_Level);
             this.backPolyLine = new kakao.maps.Polyline({
                 path: this.kakaoPathArr, // 선을 구성하는 좌표 배열입니다 클릭한 위치를 넣어줍니다
-                strokeWeight: 10, // 선의 두께입니다
+                strokeWeight: strokeWidth, // 선의 두께입니다
                 strokeColor: '#ffffff', // 선의 색깔입니다
                 strokeOpacity: 1, // 선의 불투명도입니다 0에서 1 사이값이며 0에 가까울수록 투명합니다
                 strokeStyle: 'solid', // 선의 스타일입니다
@@ -1915,7 +1838,7 @@ class EmergencyRoadObj{
 
             // 선이 그려지고 있을 때 마우스 움직임에 따라 선이 그려질 위치를 표시할 선을 생성합니다
             this.polyLine = new kakao.maps.Polyline({
-                strokeWeight: 8, // 선의 두께입니다
+                strokeWeight: strokeWidth - 2, // 선의 두께입니다
                 path: this.kakaoPathArr,
                 strokeColor: '#3386ff', // 선의 색깔입니다
                 strokeOpacity: 1, // 선의 불투명도입니다 0에서 1 사이값이며 0에 가까울수록 투명합니다
@@ -1927,6 +1850,11 @@ class EmergencyRoadObj{
         }
     }
 
+    setLineWidth() {
+        this.backPolyLine.setOptions({strokeWeight: strokeMap.get(_Level)})
+        this.polyLine.setOptions({strokeWeight: strokeMap.get(_Level) - 2})
+    }
+
     show() {
         if (this.backPolyLine) {
             this.backPolyLine.setMap(map);

+ 27 - 0
src/main/resources/static/js/signal.js

@@ -17,6 +17,8 @@ var _CvibInfoUtunImgArr = [];
 var _CvibTimeId = null;
 var overLayData;
 
+
+
 function mapZoomBound() {
 
     _Level = map.getLevel();
@@ -47,6 +49,31 @@ function mapZoomBound() {
         }
         //_CvibNodeArr = []
     }
+
+    if (_EmergencyMap && _EmergencyMap.size) {
+        _EmergencyMap.forEach((obj)=>{
+            obj.road.setLineWidth();
+            if (obj.sig && obj.sig.size) {
+                obj.sig.forEach((sig)=>{
+                   sig.setLineWidth();
+                })
+            }
+        });
+    }
+
+    const history = $('#iframeTreeList').get(0).contentWindow._historyMap;
+    if (history && history.size) {
+        history.forEach((obj)=>{
+            if (obj.get('draw')) {
+                obj.get('draw').road.setLineWidth();
+                if (obj.get('draw').sig && obj.get('draw').sig.size) {
+                    obj.get('draw').sig.forEach((sig)=>{
+                        sig.setLineWidth();
+                    })
+                }
+            }
+        });
+    }
 }
 
 function mapMoveBound() {

+ 2 - 1
src/main/resources/static/js/worker.js

@@ -13,7 +13,8 @@ self.onmessage = (e)=> {
 
         const xhr = new XMLHttpRequest();
 
-        xhr.open('POST', '/getEvpServiceList.do', true);
+        // xhr.open('POST', '/getEvpServiceList.do', true);
+        xhr.open('POST', '/getEvpsInfo.do', true);
         xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
         xhr.responseType = 'text';
         xhr.send()  //요청 전송

+ 4 - 3
src/main/webapp/WEB-INF/jsp/bottomListFrame.jsp

@@ -44,13 +44,13 @@
                 <tr>
                     <th style="width:10%">수집시각</th>
                     <th style="width:10%">센터명</th>
-                    <th style="width:15%">서비스번호</th>
+                    <th style="width:15%">서비스 ID</th>
                     <th style="width:15%">서비스명</th>
                     <th style="width:10%">긴급차량번호</th>
                     <th style="width:10%">X 좌표</th>
                     <th style="width:10%">Y 좌표</th>
-                    <th style="width:10%">속도</th>
-                    <th style="width:10%">남은거리</th>
+                    <th style="width:10%">속도(km/h)</th>
+                    <th style="width:10%">남은거리(m)</th>
                 </tr>
             </thead>
             <tbody id="evpBottomInfo"></tbody>
@@ -99,6 +99,7 @@
             return;
         }
 
+
         $('tr.on').removeClass('on');
         $(el).addClass('on');
         const map = treeFrame.get(0).contentWindow._historyMap.get(serviceId.toString());

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

@@ -160,7 +160,7 @@
                 <div class="coord-btn" onclick="moveToPosition()">이동</div>
             </div>
         </div>
-        <div class="evp_legend"><img alt="긴급차량우선신호 범례" src="/images/evp_legend2.png" width="178" height="50"></div>
+        <div class="evp_legend"><img alt="긴급차량우선신호 범례" src="/images/evp_legend.png" width="178" height="50"></div>
     </div>
     <!-- 로드뷰 -->
     <div id="rvWrapper">

+ 102 - 34
src/main/webapp/WEB-INF/jsp/treeListFrame.jsp

@@ -149,6 +149,24 @@
         }
         return year.toString() + '-' + month.toString() + '-' + day.toString();
     }
+    function getDateTimeToStr(date) {
+        let str = getDateToStr(date);
+        let hour = date.getHours();
+        let minute = date.getMinutes();
+        let seconds = date.getSeconds();
+        if (hour < 10){
+            hour = '0' + hour;
+        }
+        if (minute < 10){
+            minute = '0' + minute;
+        }
+        if (seconds < 10){
+            seconds = '0' + seconds;
+        }
+        str += ' ';
+        str += hour + ':' + minute + ':' + seconds;
+        return str;
+    }
 
     function setAddrMap(data) {
 
@@ -773,6 +791,7 @@
         imageOnOff('play', false);
         imageOnOff('stop', false);
         imageOnOff('pause', false);
+
         if (_historyMap.size) {
             _historyMap.forEach((obj)=>{
                 if (obj.get('draw')) obj.get('draw').clear();
@@ -786,12 +805,13 @@
             const bottomFrame = $('#iframeBottomList', parent.document).contents();
             bottomFrame.find('#evpBottomInfo').empty();
         }
+
         window.parent.requestService('getEvpHistory.do', param, (rec)=>{
             let str = ''
             if (rec && rec.length) {
                 for (let history of rec) {
                      _historyMap.set(history.service_id, new Map());
-                    const {event_list, phase_list} = history;
+                    const {event_list, phase_list, node_list} = history;
 
                     const dateMap = new Map();
                     if (event_list.length && phase_list.length) {
@@ -806,6 +826,7 @@
                                 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);
                         });
@@ -835,17 +856,75 @@
                                 else if (objMap.get('sig').length) {
                                     beforeSig = objMap.get('sig');
                                 }
-                                const sigData = [...beforeSig]
-
-                                sigData.forEach((obj)=>{
-                                    obj.clct_dt = date;
-                                })
 
+                                if (beforeSig.length !== node_list.length) {
+                                    for (let node of node_list) {
+                                        let idx = objMap.get('sig').findIndex(obj => obj.seq_no === node.seq_no)
+                                        if (idx === -1) {
+                                            if (new Date(date).getTime() - new Date(dateArr[0]).getTime() != 0) {
+                                                let startTime = new Date(dateArr[0]).getTime();
+                                                let currTime = new Date(date).getTime();
+                                                let beforeObj = null
+
+                                                for (let ii = currTime; ii >= startTime; ii -= 1000) {
+                                                    const key = getDateTimeToStr(new Date(ii));
+                                                    if (dateMap.get(key)) {
+                                                        const beforeIdx = dateMap.get(key).get('sig').findIndex(obj => obj.seq_no === node.seq_no);
+
+                                                        if (beforeIdx > -1) {
+                                                            beforeObj = dateMap.get(key).get('sig')[beforeIdx];
+                                                            break;
+                                                        }
+                                                    }
+                                                }
+                                                if (beforeObj) {
+                                                    beforeSig.push({
+                                                        seq_no : beforeObj.seq_no,
+                                                        clct_dt : date,
+                                                        service_id : beforeObj.service_id,
+                                                        a_end_angle : beforeObj.a_end_angle,
+                                                        a_end_lat:beforeObj.a_end_lat,
+                                                        a_end_lng:beforeObj.a_end_lng,
+                                                        a_flow_no:beforeObj.a_flow_no,
+                                                        a_head_angle:beforeObj.a_head_angle,
+                                                        a_head_lat:beforeObj.a_head_lat,
+                                                        a_head_lng: beforeObj.a_head_lng,
+                                                        a_mid_lat:beforeObj.a_mid_lat,
+                                                        a_mid_lng:beforeObj.a_mid_lng,
+                                                        a_ring_phase:beforeObj.a_ring_phase,
+                                                        b_end_angle:beforeObj.b_end_angle,
+                                                        b_end_lat:beforeObj.b_end_lat,
+                                                        b_end_lng:beforeObj.b_end_lng,
+                                                        b_flow_no:beforeObj.b_flow_no,
+                                                        b_head_angle:beforeObj.b_head_angle,
+                                                        b_head_lat:beforeObj.b_head_lat,
+                                                        b_head_lng:beforeObj.b_head_lng,
+                                                        b_mid_lat:beforeObj.b_mid_lat,
+                                                        b_mid_lng:beforeObj.b_mid_lng,
+                                                        b_ring_phase:beforeObj.b_ring_phase,
+                                                        hold_phase:beforeObj.hold_phase,
+                                                        lat : beforeObj.lat,
+                                                        lng : beforeObj.lng,
+                                                        node_id : beforeObj.node_id,
+                                                        node_nm : beforeObj.node_nm,
+                                                        plan_class:beforeObj.plan_class,
+                                                        rem_dist:beforeObj.rem_dist,
+                                                        state : beforeObj.state,
+                                                    })
+                                                }
+
+                                            }
+
+                                        }
+                                    }
+                                }
+                                const sigData = beforeSig;
                                 history.event_list.push(eventData);
                                 history.phase_list.push(sigData);
                             }
                         }
                     }
+
                      _historyMap.get(history.service_id).set('obj', history);
                      _historyMap.get(history.service_id).set('draw', null);
                     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>`;
@@ -874,7 +953,8 @@
                     beforeDraw.clear();
                     obj.set('draw', null);
                 }
-            })
+            });
+
             if (_historyMap.get(serviceId).get('draw')) {
                 return;
             }
@@ -948,9 +1028,8 @@
         }
 
 
-
         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+'">'
+        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>'
@@ -1027,34 +1106,23 @@
 
     function setSigState(phase, draw) {
         phase.forEach((sigObj)=>{
-            const evpSig = draw.sig.get(sigObj.service_id + '_' + sigObj.seq_no);
-            if (evpSig === undefined) {
+            if (draw.sig.get(sigObj.service_id + '_' + sigObj.seq_no) === 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);
-            }
+            const evpSig = draw.sig.get(sigObj.service_id + '_' + sigObj.seq_no);
+            evpSig.createRing(sigObj);
         });
+
+        draw.sig.forEach((obj, key)=>{
+            const idx = phase.findIndex((sig)=>{
+                return sig.service_id + '_' + sig.seq_no === key;
+            })
+
+            if (idx === -1) {
+                obj.deleteRing();
+                obj.setState(1);
+            }
+        })
     }
 
     function pauseSimulation() {