Quellcode durchsuchen

update 2024-10-08

junggilpark vor 1 Jahr
Ursprung
Commit
a7175e1f5e
38 geänderte Dateien mit 4013 neuen und 527 gelöschten Zeilen
  1. 9 0
      pom.xml
  2. 0 1
      src/main/java/com/its/web/ItsWebServerApplication.java
  3. 8 0
      src/main/java/com/its/web/controller/traffic/IntersectionController.java
  4. 1 0
      src/main/java/com/its/web/controller/view/ViewController.java
  5. 22 0
      src/main/java/com/its/web/dto/statistics/IxrMngmDto.java
  6. 4 0
      src/main/java/com/its/web/dto/statistics/TrafficStatisticsDto.java
  7. 5 5
      src/main/java/com/its/web/interceptor/ConnectHistory.java
  8. 5 0
      src/main/java/com/its/web/mapper/itcs/IntersectionMapper.java
  9. 6 1
      src/main/java/com/its/web/service/statistics/StatisticsService.java
  10. 3 0
      src/main/java/com/its/web/service/traffic/IntersectionService.java
  11. 26 0
      src/main/resources/application.yml
  12. 57 5
      src/main/resources/mybatis/mapper/itcs/InterSectionMapper.xml
  13. 90 9
      src/main/resources/static/css/center.css
  14. 88 12
      src/main/resources/static/css/common.css
  15. 138 77
      src/main/resources/static/css/main.css
  16. 644 0
      src/main/resources/static/css/main_org.css
  17. 10 2
      src/main/resources/static/css/statistics.css
  18. 16 10
      src/main/resources/static/css/traffic.css
  19. BIN
      src/main/resources/static/images/background/box_pattern.gif
  20. BIN
      src/main/resources/static/images/background/center_img1.jpg
  21. BIN
      src/main/resources/static/images/icon/select_position.png
  22. 22 7
      src/main/resources/static/js/statistics/statistics.js
  23. 204 138
      src/main/resources/static/js/traffic/traffic.js
  24. 2204 0
      src/main/resources/static/js/traffic/traffic_new.js
  25. 6 0
      src/main/resources/templates/admin/conn-statistics.html
  26. 53 33
      src/main/resources/templates/center/center.html
  27. 161 138
      src/main/resources/templates/include/header.html
  28. 35 65
      src/main/resources/templates/main/main.html
  29. 151 0
      src/main/resources/templates/main/main_org.html
  30. 5 1
      src/main/resources/templates/statistics/congest-stat.html
  31. 18 4
      src/main/resources/templates/statistics/tfvl-stat-amount.html
  32. 4 1
      src/main/resources/templates/statistics/tfvl-stat-speed.html
  33. 3 3
      src/main/resources/templates/traffic/cctv.html
  34. 3 3
      src/main/resources/templates/traffic/incident.html
  35. 3 3
      src/main/resources/templates/traffic/intersection.html
  36. 3 3
      src/main/resources/templates/traffic/parking.html
  37. 3 3
      src/main/resources/templates/traffic/realtimetraffic.html
  38. 3 3
      src/main/resources/templates/traffic/vms.html

+ 9 - 0
pom.xml

@@ -43,6 +43,15 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+            <version>1.6.13</version>
+        </dependency>
 
         <dependency>
             <groupId>com.tibero</groupId>

+ 0 - 1
src/main/java/com/its/web/ItsWebServerApplication.java

@@ -6,7 +6,6 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfi
 
 @SpringBootApplication(exclude = SecurityAutoConfiguration.class)
 public class ItsWebServerApplication {
-
     public static void main(String[] args) {
         SpringApplication.run(ItsWebServerApplication.class, args);
     }

+ 8 - 0
src/main/java/com/its/web/controller/traffic/IntersectionController.java

@@ -2,6 +2,7 @@ package com.its.web.controller.traffic;
 
 import com.its.web.dto.admin.HmpgUseYnDto;
 import com.its.web.dto.message.ResultDto;
+import com.its.web.dto.traffic.IntersectionCameraDto;
 import com.its.web.dto.traffic.IntersectionDto;
 import com.its.web.service.traffic.IntersectionService;
 import io.swagger.annotations.Api;
@@ -40,4 +41,11 @@ public class IntersectionController {
     public ResultDto updateCctvHmpgUseYn(@ModelAttribute HmpgUseYnDto param) {
         return this.service.updateCctvHmpgUseYn(param);
     }
+
+    @ApiOperation(value = "04.스마트 교차로 카메라 리스트 조회(CMRA_MNGM)", response = IntersectionDto.class, responseContainer = "ArrayList")
+    @PostMapping(value = "/detail-list", produces = {"application/json; charset=utf-8"})
+    @ResponseBody
+    public List<IntersectionCameraDto> findIntersectionCameraList() {
+        return service.findAllIntersectionCamera();
+    }
 }

+ 1 - 0
src/main/java/com/its/web/controller/view/ViewController.java

@@ -99,6 +99,7 @@ public class ViewController {
         model.addAttribute("selected", "statistics");
         model.addAttribute("active", "traffic01");
         model.addAttribute("road", statisticsService.findIntersectionAtrdName());
+        model.addAttribute("ixr", statisticsService.findIntersectionName());
         return "statistics/tfvl-stat-amount";
     }
 

+ 22 - 0
src/main/java/com/its/web/dto/statistics/IxrMngmDto.java

@@ -0,0 +1,22 @@
+package com.its.web.dto.statistics;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("IxrMngmDto(교통량 분석 교차로명 DTO)")
+public class IxrMngmDto {
+    @ApiModelProperty("교차로 ID")
+    @JsonProperty("ixr_id")
+    String ixrId;
+
+    @ApiModelProperty("교차로 명칭")
+    @JsonProperty("ixr_nm")
+    String ixrNm;
+}

+ 4 - 0
src/main/java/com/its/web/dto/statistics/TrafficStatisticsDto.java

@@ -176,6 +176,10 @@ public class TrafficStatisticsDto {
         @ApiModelProperty(value = "도로명, Nullable = Y", example = "새천년도로", required = false)
         @JsonProperty("ATRD_NM")
         private String atrdNm;
+
+        @ApiModelProperty(value = "교차로 번호, Nullable = Y", example = "I001", required = false)
+        @JsonProperty("IXR_ID")
+        private String ixrId;
     }
 
 }

+ 5 - 5
src/main/java/com/its/web/interceptor/ConnectHistory.java

@@ -47,7 +47,7 @@ public class ConnectHistory implements HandlerInterceptor {
         boolean isHostIp = Arrays.asList(ipAddressArr).contains(hostIp);
         String redirectPath = null;
         if (uri.contains("/phits")) {
-            if (isHostIp) {
+//            if (isHostIp) {
                 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                 boolean isLogin = (authentication != null && !(authentication instanceof AnonymousAuthenticationToken) && authentication.isAuthenticated());
 
@@ -58,10 +58,10 @@ public class ConnectHistory implements HandlerInterceptor {
                     redirectPath = "/phits";
                 }
 
-            }
-            else {
-                redirectPath = "/";
-            }
+//            }
+//            else {
+//                redirectPath = "/";
+//            }
         }
 
         if (redirectPath != null) {

+ 5 - 0
src/main/java/com/its/web/mapper/itcs/IntersectionMapper.java

@@ -2,6 +2,7 @@ package com.its.web.mapper.itcs;
 
 import com.its.web.dto.admin.HmpgUseYnDto;
 import com.its.web.dto.statistics.DaeroMngmDto;
+import com.its.web.dto.statistics.IxrMngmDto;
 import com.its.web.dto.statistics.TrafficStatisticsDto;
 import com.its.web.dto.traffic.IntersectionCameraDto;
 import com.its.web.dto.traffic.IntersectionDto;
@@ -16,6 +17,8 @@ public interface IntersectionMapper {
 
     List<IntersectionCameraDto> findAllIntersectionDetail();
 
+    List<IntersectionCameraDto> findAllIntersectionCamera();
+
     List<DaeroMngmDto> findIntersectionAtrdName();
 
     List<TrafficStatisticsDto> findStatisticsTrafficAmountByMonth(Map<String, Object> paramMap);
@@ -23,4 +26,6 @@ public interface IntersectionMapper {
     List<TrafficStatisticsDto> findStatisticsTrafficAmountByDay(Map<String, Object> paramMap);
 
     int updateCctvHmpgUseYn(HmpgUseYnDto param);
+
+    List<IxrMngmDto> findIxrName();
 }

+ 6 - 1
src/main/java/com/its/web/service/statistics/StatisticsService.java

@@ -1,6 +1,7 @@
 package com.its.web.service.statistics;
 
 import com.its.web.dto.statistics.DaeroMngmDto;
+import com.its.web.dto.statistics.IxrMngmDto;
 import com.its.web.dto.statistics.TrafficStatisticsCongestDto;
 import com.its.web.dto.statistics.TrafficStatisticsDto;
 import com.its.web.mapper.itcs.IntersectionMapper;
@@ -27,12 +28,13 @@ public class StatisticsService {
         String fromDt   = paramInfo.getFromDt();
         String toDt     = paramInfo.getToDt();
         String roadNmbr = paramInfo.getRoadNmbr();
+        String ixrId = paramInfo.getIxrId();
         List<TrafficStatisticsDto> result = new ArrayList<>();
         if (fromDt != null && toDt != null && roadNmbr != null && searchType != null) {
             paramMap.put("FROM_DT", fromDt);
             paramMap.put("TO_DT", toDt);
             paramMap.put("ROAD_NMBR", roadNmbr);
-
+            paramMap.put("IXR_ID", ixrId);
             if (searchType.equals("mn")) {
                 result = ixrMapper.findStatisticsTrafficAmountByMonth(paramMap);
             }
@@ -96,4 +98,7 @@ public class StatisticsService {
         return this.mapper.findStatisticsCongest(paramMap);
     }
 
+    public List<IxrMngmDto> findIntersectionName() {
+        return ixrMapper.findIxrName();
+    }
 }

+ 3 - 0
src/main/java/com/its/web/service/traffic/IntersectionService.java

@@ -70,6 +70,9 @@ public class IntersectionService {
         return  new ArrayList<>(result.values());
     }
 
+    public List<IntersectionCameraDto> findAllIntersectionCamera() {
+        return this.mapper.findAllIntersectionCamera();
+    }
     /**
      * 스마트 교차로 전체 정보를 목록으로 조회하여 리턴
      * @return

+ 26 - 0
src/main/resources/application.yml

@@ -13,6 +13,32 @@ server:
     include-exception: false
     include-stacktrace: never
 
+#management:
+#  security:
+#    enabled: false
+#  server:
+#    port: 9292
+#  endpoint:
+#    metrics:
+#      enabled: true
+#    prometheus:
+#      enabled: true
+#    health:
+#      show-details: always
+#      show-components: always
+#      probes:
+#        enabled: true
+#    shutdown:
+#      enabled: true
+#    info:
+#      env:
+#        enabled: true
+#      enabled: true
+#  endpoints:
+#    web:
+#      exposure:
+#        include: prometheus, metrics, info, health, shutdown, beans
+
 spring:
   output:
     ansi:

+ 57 - 5
src/main/resources/mybatis/mapper/itcs/InterSectionMapper.xml

@@ -55,6 +55,39 @@
        WHERE CDM.drct_dvsn_cd=ALS.drct_dvsn_cd
     </select>
 
+    <select id="findAllIntersectionCamera" resultType="com.its.web.dto.traffic.IntersectionCameraDto">
+        SELECT
+            CM.ixr_id,
+            CM.cmra_id,
+            CDM.drct_dvsn_cd,
+            CDM.drct_lctn,
+            CDM.bus_dvrs_lane_en,
+            CDM.srvc_anly_en,
+            CM.cmra_ip,
+            CM.cmra_port,
+            CM.cmra_http_port,
+            CM.cmra_url,
+            CM.cmra_type,
+            CM.cmra_y_crdn AS cmra_x_crdn,
+            CM.cmra_x_crdn AS cmra_y_crdn,
+            CM.cmra_angn,
+            ALS.acrd_los,
+            ALS.dely_hh,
+            CM.hmpg_dspl_en AS cmra_use_yn,
+            CM.hmpg_cmra_url,
+            CM.istl_lctn AS istl_lctn
+        FROM CMRA_MNGM CM
+                 JOIN CMRA_DRCT_MNGM CDM
+                      ON CM.ixr_id = CDM.ixr_id
+                          AND CM.cmra_id = CDM.cmra_id
+                 JOIN ACRD_LOS_STTS ALS
+                      ON CM.ixr_id = ALS.ixr_id
+                          AND CDM.drct_dvsn_cd = ALS.drct_dvsn_cd
+        WHERE CDM.drct_dvsn_cd=ALS.drct_dvsn_cd
+          AND CM.hmpg_dspl_en = 1
+    </select>
+
+
     <select id="findIntersectionAtrdName" resultType="com.its.web.dto.statistics.DaeroMngmDto">
         SELECT
             nmbr,
@@ -67,6 +100,8 @@
         SELECT
            L.STRT_IXR AS strt_name,
            L.END_IXR AS end_name,
+           L.IXR_ID AS ixr_id,
+           L.IXR_NM AS ixr_nm,
            T.T01 AS t01,
            T.T02 AS t02,
            T.T03 AS t03,
@@ -139,11 +174,14 @@
                       AND TO_DATE(#{TO_DT}, 'YYYY-MM-DD HH24:MI:SS')
                   GROUP BY LINK_ID
               ) T ON T.LINK_ID=L.LINK_ID
+          <if test="IXR_ID != null and !IXR_ID.equals('') and !IXR_ID.equals('ALL')">
+              AND L.IXR_ID = #{IXR_ID}
+          </if>
           INNER JOIN DAERO_MNGM D ON L.DAERO_DVSN=D.NMBR
-          <if test="ROAD_NMBR != 'ALL' and ROAD_NMBR != null">
+          <if test="ROAD_NMBR != null and !ROAD_NMBR.equals('') and !ROAD_NMBR.equals('ALL')">
               WHERE D.NMBR=#{ROAD_NMBR}
           </if>
-          GROUP BY T.LINK_ID, L.STRT_IXR, L.END_IXR, T01, T02, T03, T04, T05, T06, T07, T08,
+          GROUP BY T.LINK_ID, L.IXR_ID, L.IXR_NM, L.STRT_IXR, L.END_IXR, T01, T02, T03, T04, T05, T06, T07, T08,
                    T09, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23,
                    T24, T25, T26, T27, T28, T29, T30, T31
     </select>
@@ -153,6 +191,8 @@
         SELECT
             L.STRT_IXR AS strt_name,
             L.END_IXR AS end_name,
+            L.IXR_ID AS ixr_id,
+            L.IXR_NM AS ixr_nm,
             T.T00 AS t00,
             T.T01 AS t01,
             T.T02 AS t02,
@@ -210,11 +250,14 @@
             BETWEEN TO_DATE(#{FROM_DT}, 'YYYY-MM-DD HH24:MI:SS') AND TO_DATE(#{TO_DT}, 'YYYY-MM-DD HH24:MI:SS')
             GROUP BY LINK_ID
         ) T ON T.LINK_ID=L.LINK_ID
-                 INNER JOIN DAERO_MNGM D ON L.DAERO_DVSN=D.NMBR
-        <if test="ROAD_NMBR != 'ALL' and ROAD_NMBR != null">
+        <if test="IXR_ID != null and !IXR_ID.equals('') and !IXR_ID.equals('ALL')">
+            AND L.IXR_ID = #{IXR_ID}
+        </if>
+        INNER JOIN DAERO_MNGM D ON L.DAERO_DVSN=D.NMBR
+        <if test="ROAD_NMBR != null and !ROAD_NMBR.equals('') and !ROAD_NMBR.equals('ALL')">
             WHERE D.NMBR=#{ROAD_NMBR}
         </if>
-        GROUP BY T.LINK_ID, L.STRT_IXR, L.END_IXR, T00, T01, T02, T03, T04, T05, T06, T07, T08,
+        GROUP BY T.LINK_ID, L.IXR_ID, L.IXR_NM, L.STRT_IXR, L.END_IXR, T00, T01, T02, T03, T04, T05, T06, T07, T08,
                  T09, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23
     </select>
 
@@ -226,4 +269,13 @@
             AND CMRA_ID = #{cctvId}
         </if>
     </update>
+
+    <select id="findIxrName" resultType="com.its.web.dto.statistics.IxrMngmDto">
+        SELECT
+            IXR_ID,
+            IXR_NM
+        FROM
+            IXR_MNGM
+        ORDER BY IXR_NM
+    </select>
 </mapper>

+ 90 - 9
src/main/resources/static/css/center.css

@@ -91,8 +91,7 @@
     border-bottom: 1px solid rgb(33, 84, 153);
     font-size: 2rem;
 }
-.centerWrap .way-content,
-.centerWrap .content1 {
+.centerWrap .way-content{
     display: flex;
     flex-direction: row;
 }
@@ -133,7 +132,7 @@
 }
 .centerWrap .way-content > div:nth-child(2) p,
 .centerWrap .content1 > div > p {
-    padding : 10px 0;
+    padding : 10px 5px;
 }
 .centerWrap .way-content > div:nth-child(2) img {
     width: 20px;
@@ -148,20 +147,40 @@
 .centerWrap .way-content > div:nth-child(2) h2 {
     padding : 2rem 0 0 0;
     width: 100%;
-    text-align: center;
     color: rgb(51, 102, 171);
+    text-align: center;
 }
 .centerWrap .content1 h2 {
-    padding : 1rem;
     width: 100%;
-    text-align: center;
     color: rgb(51, 102, 171);
+    padding: 10px;
+    margin-top: 20px;
 }
-
-.centerWrap .content1 p {
+h2 {
+    position: relative;
+}
+.centerWrap .content1 h2:before{
+    left: 0;
+    top: 0;
+    width: 30px;
+    height: 3px;
+    background-color: rgb(51, 102, 171);
+    content: "";
+    position: absolute;
+}
+.centerWrap .content1 .box {
+    padding: 8px;
+    border: none;
+    background: url(/images/background/box_pattern.gif) repeat;
+}
+.centerWrap .content1 .box p {
     font-weight: bold;
     font-size: 15px;
+    padding: 20px;
+    background: white;
+    text-align: center;
 }
+
 .info-window {
     width: 270px;
     cursor: pointer;
@@ -188,7 +207,7 @@
 }
 
 .centerWrap .content2{
-    padding : 10px;
+    padding : 10px 10px 20px 10px;
     width: 100%;
     height: auto;
 }
@@ -206,6 +225,62 @@
     font-size: 16px;
     padding: 10px;
     text-align: center;
+    width: 50%;
+}
+
+.img-box {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 20px 10px 10px 10px;
+    border-radius: 5px;
+}
+
+.img-box img {
+    width: 100%;
+    border-radius: 15px;
+    box-shadow: 2px 2px 2px 2px #eeeeee;
+}
+
+.content3 {
+    display: flex;
+    width: 100%;
+    justify-content: space-between;
+    gap : 10px;
+}
+
+.content3 > div > div:nth-child(1)::before{
+    content: "◎ ";
+}
+.content3 > div > div:nth-child(1){
+    font-weight: bold;
+    margin-bottom: 20px;
+}
+.content3 > div > div:nth-child(2) > div::before{
+    top: 0;
+    left: 0;
+    content: '- ';
+}
+.content3 > div > div:nth-child(2) > div {
+    padding: 5px;
+}
+.content3 > div {
+    width: 50%;
+    border-radius: 5px;
+    box-sizing: border-box;
+    padding: 20px;
+}
+.content3 > div:nth-child(1) {
+    background-color: rgba(200, 151, 0, 0.1);
+    border: 1px solid rgba(169, 130, 9, 0.2);
+}
+
+.content3 > div:nth-child(2) {
+    background-color: rgba(13, 167, 176, 0.1);
+    border: 1px solid rgba(13, 167, 176, 0.2);
+}
+.centerWrap .content1 > div.content3 {
+    padding: 10px 10px 30px 10px;
 }
 
 @media (max-width: 720px) {
@@ -238,6 +313,12 @@
         width: 100%;
         align-items: center;
     }
+    .centerWrap .content1 > div.content3 {
+        flex-direction: column;
+    }
+    .centerWrap .content1 > div.content3 > div {
+        width: 100%;
+    }
 }
 
 @media (max-height: 765px) {

+ 88 - 12
src/main/resources/static/css/common.css

@@ -75,7 +75,9 @@ html, body {
 header {
     visibility: visible;
     width: 100%;
-    padding: 1.5rem 1rem;
+    height: 110px;
+    display: flex;
+    padding: 0 1rem;
     -webkit-box-align: center;
     align-items: center;
     background: rgb(255, 255, 255);
@@ -116,6 +118,7 @@ header .header-menu {
     width: 100%;
     margin: 0px auto;
     display: flex;
+    height: 100%;
     -webkit-box-pack: justify;
     justify-content: space-between;
 }
@@ -132,16 +135,21 @@ header .top-menu {
     align-items: center;
     font-weight: bold;
     line-height: 3rem;
-    border-bottom: 3px solid white;
+    width: 35px;
+    height: 100%;
 }
 
 header .top-menu-cont {
-    height: 51px;
+    height: 100%;
     display: none;
     -webkit-box-align: center;
     align-items: center;
-    margin-right: 1.5rem;
+    /*margin-right: 1.5rem;*/
     color: rgb(51, 51, 51);
+    width: calc(100% / 6);
+    justify-content: center;
+    box-sizing: border-box;
+    border-bottom: 3px solid white;
 }
 
 header .top-menu-cont.on {
@@ -157,6 +165,7 @@ header .top-menu-cont:hover {
 header .top-menu-cont a {
     text-decoration: none;
     color: inherit;
+    cursor: pointer;
 }
 
 header .top-menu-cont img {
@@ -170,7 +179,7 @@ header .top-menu span img {
 }
 
 header .top-menu span {
-    margin-left: 1rem;
+    margin-right: 1rem;
     cursor: pointer;
 }
 
@@ -290,6 +299,52 @@ header .modal-container p:hover {
     color: rgb(51 102 171);
 }
 
+header .down-menu {
+    position: absolute;
+    top: 110px;
+    left: 0;
+    width: 100%;
+    height: 210px;
+    z-index: 110;
+    background-color: white;
+    border-top: 1px solid #eeeeee;
+    border-bottom: 4px solid #3366AB;
+    justify-content: center;
+    display: none;
+    padding: 0 1rem;
+}
+header .down-menu > div{
+    max-width: 1200px;
+    width: 1200px;
+    display: flex;
+    justify-content: flex-end;
+}
+
+header .down-menu > div > div {
+    width: 500px;
+    display: flex;
+    border-left: 1px solid #eeeeee;
+}
+header .down-menu > div > div > div {
+    width: calc(100% / 6);
+    height: 100%;
+    border-right: 1px solid #eeeeee;
+}
+header .down-menu > div > div > div > div {
+    width: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 35px;
+    border-bottom: 1px solid #eeeeee;
+    flex-direction: column;
+    font-size: 12px;
+    cursor: pointer;
+}
+
+header .down-menu > div > div > div > div:hover {
+    color: #3366AB;
+}
 /* 푸터 */
 footer {
     bottom: 0px;
@@ -390,7 +445,7 @@ footer .mobile-footer {
     width: 100%;
     transition: all 1s ease 0s;
     height: 100vh;
-    z-index: 200;
+    z-index: 99999;
     position: fixed;
     top: 0px;
     left: 0px;
@@ -684,6 +739,10 @@ i {
     padding : 10px;
 }
 
+.three-line-menu {
+    display: block;
+}
+
 
 @media (max-width: 450px) {
     /* 헤더 */
@@ -872,10 +931,6 @@ i {
 }
 
 @media (min-width: 765px) {
-    /* 헤더 */
-    header .top-menu-cont {
-        display: flex;
-    }
 
     header .modal-container p > img {
         width: 20px;
@@ -897,7 +952,22 @@ i {
 
 }
 
-@media (min-width: 920px) {
+@media (min-width: 840px) {
+    .three-line-menu {
+        display: none;
+    }
+    /* 헤더 */
+    header .top-menu-cont {
+        display: flex;
+    }
+
+    header .down-menu > div > div,
+    header .top-menu {
+        width: 500px;
+    }
+}
+
+@media (min-width: 1150px) {
     /* 헤더 */
     header {
         font-size: 20px;
@@ -906,7 +976,13 @@ i {
     header .logo > p {
         font-size: 20px;
     }
-
+    header .down-menu > div > div > div > div{
+        font-size: 15px;
+    }
+    header .down-menu > div > div,
+    header .top-menu {
+        width: 700px;
+    }
 }
 
 .loading-img{

+ 138 - 77
src/main/resources/static/css/main.css

@@ -9,7 +9,8 @@
     justify-content: center;
     /*background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.3)), url(/images/background/main_bg.gif);*/
     /*background-image: url(/images/background/main_bg.gif);*/
-    background-size: cover;
+    /*background-size: cover;*/
+    background-size: 100% 100%;
     /*background-image: url("/images/background/bg_main3.jpg");*/
     background-image: url("/images/background/background_3.jpg");
     /*z-index: 1;*/
@@ -30,7 +31,7 @@
 .mainWrap .first-bg {
     background-image: url(/images/background/bg_cctv.png);
     background-color: rgb(33, 84, 153);
-    background-size: cover;
+    /*background-size: cover;*/
     background-position-y: 100%;
     position: relative;
     cursor: pointer;
@@ -55,23 +56,24 @@
     display: flex;
     -webkit-box-align: center;
     align-items: center;
+    background-size: 100% 100%;
 }
 
 .mainWrap .other-bg.stat-bg {
     background-image: url('/images/background/bg_statistics.png');
-    background-color: rgb(153, 221, 234);
+    /*background-color: rgb(153, 221, 234);*/
 }
 
 .mainWrap .road-bg {
     background-image: url('/images/background/bg_control.png');
 }
 
-.mainWrap .other-bg.bus-bg {
+.mainWrap .bus-bg {
     background-image: url('/images/background/bg_bus.png');
-    background-color: rgb(153, 221, 234);
 }
-.mainWrap .parking-bg {
+.mainWrap .other-bg.parking-bg {
     background-image: url("/images/background/bg_parking4.png");
+    background-color: rgb(153, 221, 234);
 }
 
 .mainWrap .bus-bg > div {
@@ -95,13 +97,14 @@
 
 .mainWrap .other-bg {
     background-color: rgb(204, 234, 234);
-    background-size: cover;
+    /*background-size: cover;*/
     background-position-y: 100%;
     position: relative;
     cursor: pointer;
     width: 200px;
     height: 200px;
     color: rgb(51, 51, 51);
+    background-size: 100% 100%;
 }
 
 .mainWrap .other-bg:hover {
@@ -123,11 +126,26 @@
 }
 
 .mainWrap .main-menu > .top > div {
-    width: calc(20%);
-    margin-bottom: 10px;
-    height: 400px;
+    width: calc(25%);
+    height: 100px;
     background-position: center center;
     background-repeat: no-repeat;
+    border-radius: 15px;
+    overflow: hidden;
+}
+
+.mainWrap .main-menu > .top > div:hover {
+    height: 460px;
+}
+
+
+.mainWrap .main-menu > .top > div > div {
+    width: 100%;
+    height: 460px;
+    background-position: center center;
+    background-repeat: no-repeat;
+    border-radius: 15px;
+    position: relative;
 }
 
 .mainWrap .main-menu > .top > div:not(:last-child) {
@@ -140,20 +158,22 @@ body {
 }
 
 .mainWrap .main {
-    max-width: 1200px;
+    /*max-width: 1200px;*/
+    max-width: 1700px;
     width: 95%;
     position: relative;
     display: flex;
     flex-direction: column;
     padding: 50px 0px;
     -webkit-box-pack: center;
-    justify-content: center;
+    /*justify-content: center;*/
     min-height: 529px;
     /*opacity: 0.9;*/
 }
 
 .mainWrap .main-menu {
-    max-width: 1200px;
+    /*max-width:  px;*/
+    max-width: 1700px;
     width: 100%;
     position: relative;
     /*padding: 50px 0px;*/
@@ -162,6 +182,9 @@ body {
 .mainWrap .top, .mid {
     display: flex;
 }
+.mainWrap .mid {
+    margin-bottom: 10px;
+}
 
 .mainWrap .bottom img {
     width: 30px;
@@ -256,8 +279,8 @@ body {
 }
 .mainWrap .mid > div {
     /*width: calc(33.3333%);*/
-    width: 50%;
-    height: 220px;
+    width: 100%;
+    height: 75px;
 }
 
 .mainWrap .mid > div:not(:last-child) {
@@ -341,11 +364,13 @@ body {
 
 .mainWrap .notice {
     padding: 1.5rem;
-    background-color: rgb(204, 234, 234);
+    background-color: white;
 }
 
-.mainWrap .notice > div:first-child {
-    margin-bottom: 2rem;
+.mainWrap .notice > div {
+    display: flex;
+    height: 100%;
+    align-items: center;
 }
 
 .mainWrap .notice > div:first-child img {
@@ -356,76 +381,78 @@ body {
 
 .mainWrap .incd > div:first-child span,
 .mainWrap .notice > div:first-child span {
-    display: inline-block;
-    line-height: 30px;
     color: rgb(51, 51, 51);
     font-weight: bold;
 }
 
 .mainWrap .notice > div:first-child a {
-    float: right;
+    margin-left: auto;
     font-size: 30px;
     line-height: 30px;
 }
-
-.mainWrap .notice > .content {
-    color: rgb(51, 51, 51);
+.mainWrap .notice .content {
+    width: calc(100% - 130px);
+    height: 100%;
+    overflow: hidden;
+    position: relative;
+    box-sizing: border-box;
+    padding : 0 5px;
 }
 
-.mainWrap .notice > .content > div {
-    font-size: 14px;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
+.mainWrap .notice .content > div {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-size: cover;
+    background-position: center;
+    animation: slider 7.5s infinite linear;
+    font-weight: bold;
     display: flex;
-    -webkit-box-pack: justify;
-    justify-content: space-between;
-    margin-bottom: 1.25rem;
-    /*padding-bottom: 10px;*/
-    /*position: relative;*/
+    align-items: center;
+    box-sizing: border-box;
+    padding: 0 15px;
+    cursor: pointer;
+    /*white-space: nowrap;*/
+    /*text-overflow: ellipsis;*/
 }
-
-/*.mainWrap .notice > .content > div::after{*/
-/*    background: none repeat scroll 0 0 transparent;*/
-/*    bottom: 0;*/
-/*    content: "";*/
-/*    display: block;*/
-/*    height: 1px;*/
-/*    left: 50%;*/
-/*    position: absolute;*/
-/*    background: black;*/
-/*    transition: width 0.3s ease 0s, left 0.3s ease 0s;*/
-/*    width: 0;*/
-/*}*/
-
-/*.mainWrap .notice > .content > div:hover:after {*/
-/*    width: 100%;*/
-/*    left: 0;*/
-/*}*/
-
-
-.mainWrap .notice > .content > div > div {
-    font-size: 14px;
-    overflow: hidden;
-    text-overflow: ellipsis;
+.mainWrap .notice .content > div > div:nth-child(1) {
+    width: calc(100% - 100px);
     white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
 }
 
-.mainWrap .incd > div > img,
-.mainWrap .notice > div > img {
-    display: none;
+.mainWrap .notice .content > div:nth-child(1) {
+    animation-delay: 0;
 }
-.mainWrap .incd > .incd-content > div > div:hover,
-.mainWrap .notice > .content > div > div:hover {
-    cursor: pointer;
+.mainWrap .notice .content > div:nth-child(2) {
+    animation-delay: -2.5s;
+}
+.mainWrap .notice .content > div:nth-child(3) {
+    animation-delay: -5s;
 }
 
-.mainWrap .incd > .incd-content > div > span,
-.mainWrap .notice > .content > div > span {
-    font-size: 13px;
-    color: rgb(51, 51, 51);
-    font-weight: bold;
-    display: none;
+@keyframes slider {
+    0% {
+        transform: translateX(0);
+    }
+
+    26% {
+        transform: translateX(0);
+    }
+    33% {
+        transform: translateX(-100%);
+        animation-timing-function: step-end;
+    }
+    93% {
+        transform: translateX(100%);
+    }
+
+    100% {
+        transform: translateX(0);
+    }
 }
 
 .mainWrap .mid a:hover {
@@ -473,15 +500,29 @@ body {
 
 @media (max-width: 450px) {
     .mainWrap .top {
-        display: none;
+        /*display: none;*/
+        flex-direction: column;
+        align-items: center;
+        gap: 10px;
+    }
+    .mainWrap .main-menu > .top > div {
+        width: 100%;
+        height: 350px;
+        background-size: 100% 350px;
+    }
+    .mainWrap .main-menu > .top > div:not(:last-child) {
+        margin-right: 0;
     }
-
     .mainWrap {
         /*padding: 0;*/
         /*height: calc(100% - 149.19px);*/
         padding-bottom: 78px;
     }
 
+    .mainWrap{
+        background-image: none;
+    }
+
     .mainWrap .main {
         width: 100%;
         /*height: 100%;*/
@@ -495,7 +536,8 @@ body {
     }
     .mainWrap .mid {
         flex-direction: column;
-        height: 400px;
+        /*height: 400px;*/
+        height: 50px;
     }
     .mainWrap .bottom {
         flex-direction: column;
@@ -524,13 +566,20 @@ body {
     .mainWrap .mid > div {
         width: 100%;
         /*height: calc(100% / 3);*/
-        height: 200px;
+        height: 100%;
+        align-items: center;
+    }
+    .mainWrap .notice .content {
+        width: calc(100% - 105px);
     }
-
     .mainWrap .bottom > div:first-child {
         /*margin-bottom: 0.7rem;*/
         margin-right: 0;
     }
+    .mainWrap .notice > div {
+        align-items: center;
+        font-size: 12px;
+    }
 
     .mainWrap .notice > div:first-child img,
     .mainWrap .incd > div:first-child img {
@@ -540,7 +589,7 @@ body {
     }
 
     .mainWrap .main-menu {
-        padding: 0;
+        padding: 20px;
         /*height: 100%;*/
     }
 
@@ -557,6 +606,17 @@ body {
     .mainWrap .center > div {
         font-size: 20px;
     }
+
+    .mainWrap .notice .content > div > div:nth-child(1){
+        width: 100%;
+    }
+    .mainWrap .notice .content > div > div:nth-child(2) {
+        display: none;
+    }
+
+    .mainWrap .main-menu > .top > div > div {
+        height: 100%;
+    }
 }
 
 @media (min-width: 420px) {
@@ -579,6 +639,7 @@ body {
     /*.popup_content img {*/
     /*    max-width: 320px;*/
     /*}*/
+
 }
 
 @media (min-width: 765px) {
@@ -628,7 +689,7 @@ body {
         max-width: 800px;
     }
     .mainWrap {
-        min-height: 758px;
+        min-height: 756px;
     }
     .mainWrap .bus-bg > div img {
         margin-left: 0.5rem;

+ 644 - 0
src/main/resources/static/css/main_org.css

@@ -0,0 +1,644 @@
+/* 메인화면 */
+
+.mainWrap {
+    overflow: auto;
+    width: 100%;
+    /*height: calc(100% - 208px);*/
+    display: flex;
+    -webkit-box-pack: center;
+    justify-content: center;
+    /*background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.3)), url(/images/background/main_bg.gif);*/
+    /*background-image: url(/images/background/main_bg.gif);*/
+    background-size: cover;
+    /*background-image: url("/images/background/bg_main3.jpg");*/
+    background-image: url("/images/background/background_3.jpg");
+    /*z-index: 1;*/
+}
+/*.mainWrap::after {*/
+/*    content: "";*/
+/*    width: 100%;*/
+/*    position: absolute;*/
+/*    background-image: url(/images/background/bg_main2.jpg);*/
+/*    background-size: cover;*/
+/*    top: 0;*/
+/*    left: 0;*/
+/*    z-index: -1;*/
+/*    opacity: 0.7;*/
+/*    min-height: 1015px;*/
+/*}*/
+
+.mainWrap .first-bg {
+    background-image: url(/images/background/bg_cctv.png);
+    background-color: rgb(33, 84, 153);
+    background-size: cover;
+    background-position-y: 100%;
+    position: relative;
+    cursor: pointer;
+    width: 200px;
+    height: 200px;
+}
+
+.mainWrap .first-bg:hover {
+    background-color: rgb(102, 186, 204);
+    color: black;
+}
+
+.mainWrap .first-bg > div {
+    font-size: 25px;
+    color: rgb(255, 255, 255);
+    position: absolute;
+    top: 40px;
+    width: 100%;
+    -webkit-box-pack: center;
+    justify-content: center;
+    font-weight: bold;
+    display: flex;
+    -webkit-box-align: center;
+    align-items: center;
+}
+
+.mainWrap .other-bg.stat-bg {
+    background-image: url('/images/background/bg_statistics.png');
+    background-color: rgb(153, 221, 234);
+}
+
+.mainWrap .road-bg {
+    background-image: url('/images/background/bg_control.png');
+}
+
+.mainWrap .other-bg.bus-bg {
+    background-image: url('/images/background/bg_bus.png');
+    background-color: rgb(153, 221, 234);
+}
+.mainWrap .parking-bg {
+    background-image: url("/images/background/bg_parking4.png");
+}
+
+.mainWrap .bus-bg > div {
+    font-size: 25px;
+    position: absolute;
+    top: 40px;
+    width: 100%;
+    -webkit-box-pack: center;
+    justify-content: center;
+    font-weight: bold;
+    display: flex;
+    -webkit-box-align: center;
+    align-items: center;
+}
+
+.mainWrap .bus-bg > div img {
+    width: 20px;
+    height: 20px;
+    margin-left: 0;
+}
+
+.mainWrap .other-bg {
+    background-color: rgb(204, 234, 234);
+    background-size: cover;
+    background-position-y: 100%;
+    position: relative;
+    cursor: pointer;
+    width: 200px;
+    height: 200px;
+    color: rgb(51, 51, 51);
+}
+
+.mainWrap .other-bg:hover {
+    background-color: rgb(33, 84, 153);
+    color: rgb(255, 255, 255);
+}
+
+.mainWrap .other-bg > div {
+    font-size: 25px;
+    position: absolute;
+    top: 40px;
+    width: 100%;
+    -webkit-box-pack: center;
+    justify-content: center;
+    font-weight: bold;
+    display: flex;
+    -webkit-box-align: center;
+    align-items: center;
+}
+
+.mainWrap .main-menu > .top > div {
+    width: calc(20%);
+    margin-bottom: 10px;
+    height: 400px;
+    background-position: center center;
+    background-repeat: no-repeat;
+}
+
+.mainWrap .main-menu > .top > div:not(:last-child) {
+    margin-right: 10px;
+}
+
+body {
+    width: 100%;
+    height: 100%;
+}
+
+.mainWrap .main {
+    max-width: 1200px;
+    width: 95%;
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    padding: 50px 0px;
+    -webkit-box-pack: center;
+    justify-content: center;
+    min-height: 529px;
+    /*opacity: 0.9;*/
+}
+
+.mainWrap .main-menu {
+    max-width: 1200px;
+    width: 100%;
+    position: relative;
+    /*padding: 50px 0px;*/
+}
+
+.mainWrap .top, .mid {
+    display: flex;
+}
+
+.mainWrap .bottom img {
+    width: 30px;
+    height: 30px;
+    margin-right: 1rem;
+}
+
+/*.mainWrap .bottom > div:first-child {*/
+/*    width: 100%;*/
+/*    height: 40px;*/
+/*}*/
+/*.mainWrap .bottom > div:nth-child(2) {*/
+/*    width: 100%;*/
+/*    height: 300px;*/
+/*}*/
+/*.mainWrap .bottom > div:nth-child(2) > div {*/
+/*    width: 50%;*/
+/*    height: 100%;*/
+/*}*/
+
+.mainWrap .bottom {
+    margin-top: 10px;
+    margin-bottom: 10px;
+    font-size: 16px;
+    font-weight: bold;
+    /*padding: 20px;*/
+    /*background-color: white;*/
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    /*flex-direction: column;*/
+    width: 100%;
+    height: 350px;
+    background-size: 100% 350px;
+    /*background : white;*/
+    /*background-image: url("/images/background/bg_main3.jpg");*/
+}
+.mainWrap .bottom > div {
+    width: calc(50% - 5px);
+    /*width: 100%;*/
+    height: 100%;
+    /*padding: 1.5rem;*/
+    /*box-shadow: 2px 2px 2px 2px #eeeeee;*/
+}
+
+.mainWrap .bottom > div:first-child {
+    margin-right: 5px;
+}
+.mainWrap .bottom > div:nth-child(2) {
+    margin-left: 5px;
+    background-color: #cceaea;
+    padding: 1.5rem;
+}
+.mainWrap .bottom .first-box {
+    text-align: right;
+    color: black;
+}
+.mainWrap .bottom .first-box h1,
+.mainWrap .bottom .first-box div {
+    margin-top: 20px;
+}
+/*.mainWrap .bottom > div:not(:last-child) {*/
+/*    margin-right: 10px;*/
+/*}*/
+.mainWrap .bottom > div:nth-child(2) {
+    display: flex;
+    flex-direction: column;
+    /*align-items: center;*/
+    align-items: flex-start;
+}
+.mainWrap .bottom > div:nth-child(2) > div:first-child{
+    width: 100%;
+    height: 30px;
+    margin-bottom: 10px;
+    border-radius: 5%;
+}
+
+.mainWrap .bottom .video-box {
+    width: 100%;
+    height: calc(100% - 40px);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+.mainWrap .bottom video {
+    width: 85%;
+    height: calc(100% - 40px);
+    margin: auto;
+    /*height: 100%;*/
+    border-radius: 15px;
+    background-color: black;
+}
+.mainWrap .mid > div {
+    /*width: calc(33.3333%);*/
+    width: 50%;
+    height: 220px;
+}
+
+.mainWrap .mid > div:not(:last-child) {
+    margin-right: 10px;
+}
+
+.mainWrap .center {
+    background-image: url(/images/background/bg_center.jpg);
+    background-size: 100% 100%;
+    /*background-position-y: 100%;*/
+    position: relative;
+    cursor: pointer;
+    width: 100%;
+    height: 100%;
+}
+
+.mainWrap .center > div {
+    font-size: 25px;
+    color: rgb(255, 255, 255);
+    position: absolute;
+    top: 75%;
+    width: 100%;
+    -webkit-box-pack: center;
+    justify-content: center;
+    font-weight: bold;
+    display: flex;
+    -webkit-box-align: center;
+    align-items: center;
+}
+
+.mainWrap .incd {
+    padding: 1.5rem;
+    background-color: rgb(255, 255, 255);
+    /*box-shadow: 2px 2px 2px 2px #eeeeee;*/
+}
+
+.mainWrap .incd > div:first-child {
+    margin-bottom: 2rem;
+}
+
+.mainWrap .incd > div img {
+    width: 30px;
+    height: 30px;
+    margin-right: 1rem;
+}
+
+/*.mainWrap .incd > div span {*/
+/*    display: inline-block;*/
+/*    line-height: 30px;*/
+/*    color: rgb(51, 51, 51);*/
+/*    font-weight: bold;*/
+/*}*/
+
+.mainWrap .incd > div a {
+    float: right;
+    font-size: 30px;
+    line-height: 30px;
+}
+
+.mainWrap .incd-content {
+    color: rgb(51, 51, 51);
+}
+
+.mainWrap .incd-content > div {
+    font-size: 14px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    display: flex;
+    -webkit-box-pack: justify;
+    justify-content: space-between;
+    margin-bottom: 1.25rem;
+}
+
+.mainWrap .incd-content > div > div {
+    font-size: 14px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.mainWrap .notice {
+    padding: 1.5rem;
+    background-color: rgb(204, 234, 234);
+}
+
+.mainWrap .notice > div:first-child {
+    margin-bottom: 2rem;
+}
+
+.mainWrap .notice > div:first-child img {
+    width: 30px;
+    height: 30px;
+    margin-right: 1rem;
+}
+
+.mainWrap .incd > div:first-child span,
+.mainWrap .notice > div:first-child span {
+    display: inline-block;
+    line-height: 30px;
+    color: rgb(51, 51, 51);
+    font-weight: bold;
+}
+
+.mainWrap .notice > div:first-child a {
+    float: right;
+    font-size: 30px;
+    line-height: 30px;
+}
+
+.mainWrap .notice > .content {
+    color: rgb(51, 51, 51);
+}
+
+.mainWrap .notice > .content > div {
+    font-size: 14px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    display: flex;
+    -webkit-box-pack: justify;
+    justify-content: space-between;
+    margin-bottom: 1.25rem;
+    /*padding-bottom: 10px;*/
+    /*position: relative;*/
+}
+
+/*.mainWrap .notice > .content > div::after{*/
+/*    background: none repeat scroll 0 0 transparent;*/
+/*    bottom: 0;*/
+/*    content: "";*/
+/*    display: block;*/
+/*    height: 1px;*/
+/*    left: 50%;*/
+/*    position: absolute;*/
+/*    background: black;*/
+/*    transition: width 0.3s ease 0s, left 0.3s ease 0s;*/
+/*    width: 0;*/
+/*}*/
+
+/*.mainWrap .notice > .content > div:hover:after {*/
+/*    width: 100%;*/
+/*    left: 0;*/
+/*}*/
+
+
+.mainWrap .notice > .content > div > div {
+    font-size: 14px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.mainWrap .incd > div > img,
+.mainWrap .notice > div > img {
+    display: none;
+}
+.mainWrap .incd > .incd-content > div > div:hover,
+.mainWrap .notice > .content > div > div:hover {
+    cursor: pointer;
+}
+
+.mainWrap .incd > .incd-content > div > span,
+.mainWrap .notice > .content > div > span {
+    font-size: 13px;
+    color: rgb(51, 51, 51);
+    font-weight: bold;
+    display: none;
+}
+
+.mainWrap .mid a:hover {
+    color: #0d6efd;
+}
+.popup_content {
+    width: auto;
+    height: auto;
+    position: absolute;
+    background-color: white;
+    border: 1px solid #7c7b7b;
+    box-shadow: 0 0 5px #000;
+}
+
+.popup_content > div:nth-child(1) {
+    cursor: pointer;
+}
+
+.popup_content > .close-box{
+    background-color: black;
+    color: white;
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    height: 40px;
+    gap: 5px;
+    padding-right: 10px;
+}
+
+.popup_content > .close-box > input,
+.popup_content > .close-box > label,
+.popup_content > .close-box > div {
+    cursor: pointer;
+}
+.popup_content > .close-box > div {
+    margin-left: 5px;
+}
+
+.popup_content {
+    max-width: 320px;
+}
+.popup_content img {
+    max-width: 320px;
+}
+
+@media (max-width: 450px) {
+    .mainWrap .top {
+        display: none;
+    }
+
+    .mainWrap {
+        /*padding: 0;*/
+        /*height: calc(100% - 149.19px);*/
+        padding-bottom: 78px;
+    }
+
+    .mainWrap .main {
+        width: 100%;
+        /*height: 100%;*/
+        padding: 0;
+        justify-content: unset;
+
+    }
+    .mainWrap .bottom {
+        margin-top: 0;
+        margin-bottom: 0;
+    }
+    .mainWrap .mid {
+        flex-direction: column;
+        height: 400px;
+    }
+    .mainWrap .bottom {
+        flex-direction: column;
+        height: 450px;
+    }
+    .mainWrap .bottom .first-box {
+        padding: 1.5rem 1.5rem 0.5rem 1.5rem;
+        height: 135px;
+    }
+    /*.mainWrap .bottom > div:nth-child(2) > div:first-child{*/
+    /*    width: 100%;*/
+    /*    height: 200px;*/
+    /*    margin-right: 0;*/
+    /*}*/
+    .mainWrap .bottom > div:nth-child(2) {
+        height: 250px;
+        /*padding: 0 1rem 1rem 1rem;*/
+        padding : 1rem;
+        margin-left: 0;
+    }
+    .mainWrap .bottom video {
+        width: 100%;
+        height: calc(100% - 30px);
+    }
+    .mainWrap .bottom > div,
+    .mainWrap .mid > div {
+        width: 100%;
+        /*height: calc(100% / 3);*/
+        height: 200px;
+    }
+
+    .mainWrap .bottom > div:first-child {
+        /*margin-bottom: 0.7rem;*/
+        margin-right: 0;
+    }
+
+    .mainWrap .notice > div:first-child img,
+    .mainWrap .incd > div:first-child img {
+        display: inline;
+        width: 23px;
+        height: 23px;
+    }
+
+    .mainWrap .main-menu {
+        padding: 0;
+        /*height: 100%;*/
+    }
+
+    .mainWrap .notice,
+    .mainWrap .incd {
+        padding: 1rem 1.5rem;
+    }
+
+    .mainWrap .incd > .incd-content > div > span,
+    .mainWrap .notice > .content > div > span {
+        display: inline;
+    }
+
+    .mainWrap .center > div {
+        font-size: 20px;
+    }
+}
+
+@media (min-width: 420px) {
+
+    /* 메인화면 */
+    .mainWrap .center > div,
+    .mainWrap .other-bg > div,
+    .mainWrap .first-bg > div {
+        font-size: 15px;
+    }
+
+
+    .mainWrap .bus-direction {
+        display: none;
+    }
+
+    /*.popup_content {*/
+    /*    max-width: 320px;*/
+    /*}*/
+    /*.popup_content img {*/
+    /*    max-width: 320px;*/
+    /*}*/
+}
+
+@media (min-width: 765px) {
+
+    /* 메인화면 */
+    .mainWrap .center > div,
+    .mainWrap .other-bg > div,
+    .mainWrap .first-bg > div {
+        font-size: 16px;
+    }
+
+    .mainWrap .bus-direction {
+        display: block;
+    }
+
+    .mainWrap .incd > div > img,
+    .mainWrap .notice > div > img {
+        display: inline;
+    }
+
+    .popup_content {
+        max-width: 600px;
+    }
+    .popup_content img {
+        max-width: 600px;
+    }
+}
+
+@media (min-width: 920px) {
+
+    /* 메인화면 */
+    .mainWrap .center > div,
+    .mainWrap .other-bg > div,
+    .mainWrap .first-bg > div {
+        font-size: 20px;
+    }
+
+    .mainWrap .incd > .incd-content > div > span,
+    .mainWrap .notice > .content > div > span {
+        display: inline;
+    }
+
+    .popup_content {
+        max-width: 800px;
+    }
+    .popup_content img {
+        max-width: 800px;
+    }
+    .mainWrap {
+        min-height: 758px;
+    }
+    .mainWrap .bus-bg > div img {
+        margin-left: 0.5rem;
+    }
+}
+
+
+@media (max-height: 900px) {
+    /* 메인화면 */
+    /*.mainWrap .main-menu > .top > div {*/
+    /*    height: 200px;*/
+    /*}*/
+}

+ 10 - 2
src/main/resources/static/css/statistics.css

@@ -122,11 +122,11 @@
 }
 .statisticWrap .search-bar > div:nth-child(1) select{
     width: 100%;
-    min-width: 90px;
+    min-width: 70px;
     max-width: 300px;
     height: 34px;
     line-height: 22px;
-    padding: 6px 27px 6px 11px;
+    padding: 6px 24px 6px 6px;
     outline: none;
     color: rgb(255, 255, 255);
     border: 1px solid rgb(255, 255, 255);
@@ -344,6 +344,14 @@
         max-width: 180px;
         min-width: 180px;
     }
+
+    .statisticWrap .search-bar > div:nth-child(1) select {
+        background: url(/images/icon/select.png) right 4px center / 10px no-repeat;
+        padding: 6px 17px 6px 6px;
+    }
+    .statisticWrap .search-bar .label{
+        font-size: 12px;
+    }
 }
 
 @media (max-width: 547px) {

+ 16 - 10
src/main/resources/static/css/traffic.css

@@ -255,7 +255,10 @@ ul, li {
     color: #a59d9d;
 }
 .left-list-area .list-content > ul > li .traffic-list {
-    display: flex; padding: 10px; border-bottom: 1px solid #c3c1c1; cursor: pointer;
+    display: flex; padding: 10px; border-bottom: 1px solid #c3c1c1; cursor: pointer; box-sizing: border-box;
+}
+.left-list-area .list-content > ul > li .traffic-list.on {
+    border-bottom: 2px solid #0054ba;
 }
 .left-list-area .list-content > ul > li {
     padding: 0 15px;
@@ -410,7 +413,8 @@ ul, li {
     list-style: none;
     outline-width: 0px;
     float: left;
-    width: calc(100% / 6);
+    /*width: calc(100% / 6);*/
+    width: calc(100% / 5);
     height: 100%;
     border-left: 1px solid rgb(209, 209, 209);
     box-sizing: border-box;
@@ -983,23 +987,25 @@ ul, li {
         right: 10px;
         bottom : 10px;
     }
-
+    .left-list-area .list-content {
+        height: calc(100% - 97px)
+    }
     .left-list-area {
-        height: 302px;
-        width: 53px;
+        /*height: 276px;*/
+        /*width: 53px;*/
     }
 
     .left-list-area .list-tab {
-        flex-direction: column;
-        height: 301px;
+        /*flex-direction: column;*/
+        /*height: 276px;*/
     }
     .left-list-area .list-tab > li {
         width: 100%;
         height: 55px;
     }
     .left-list-area .list-tab > li:not(:last-child) {
-        border-right: 0px;
-        border-bottom: 1px solid #c3c1c1;
+        /*border-right: 0px;*/
+        /*border-bottom: 1px solid #c3c1c1;*/
     }
 
     .left-list-area .list-search,
@@ -1009,7 +1015,7 @@ ul, li {
     .location-btn,
     .location-box,
     .legend-bottom {
-        display: none;
+        /*display: none;*/
     }
 
     .cctv-info-window {

BIN
src/main/resources/static/images/background/box_pattern.gif


BIN
src/main/resources/static/images/background/center_img1.jpg


BIN
src/main/resources/static/images/icon/select_position.png


+ 22 - 7
src/main/resources/static/js/statistics/statistics.js

@@ -2,11 +2,10 @@ const emptyStr = `<tr>
                     <td><span class="empty-content">조회 된 항목이 없습니다.</span></td>
                   </tr>`;
 
-let _date, _atrdNm, _statStr, _type;
+let _date, _atrdNm, _type;
 
 $(()=>{
     init();
-    _statStr = $('.menu > div.active').text();
 })
 
 function init () {
@@ -18,6 +17,7 @@ function init () {
     const $comBtn = $('.button.comm');
     const $conBtn = $('.button.congest');
     const $toggle = $('.table-toggle');
+    const $roadType = $('.road_type');
 
     const now      = new Date();
     const nowYear  = now.getFullYear();
@@ -28,6 +28,16 @@ function init () {
     $comBtn.on('click', ()=> searchStatisticsComm());
     $conBtn.on('click', ()=> searchStatisticsCongest());
 
+    if ($roadType[0]) {
+        $roadType.on('change', function() {
+            //const isRoad = $(this).val() === 'road';
+            const $road = $('.road');
+            const $ixr  = $('.ixr');
+            $road.toggle();
+            $ixr.toggle();
+        })
+    }
+
     $type.on('change', function(){
         const isDisabled = !($(this).val() === 'dd');
         $date.attr('disabled', isDisabled);
@@ -151,12 +161,16 @@ function searchStatisticsAmount() {
     const $date     = $('.date');
     const $thead    = $('.thead');
     const $tbody    = $('.table-content');
-    const $table    = $('.table-box table')
+    const $table    = $('.table-box table');
+    const $ixrId    = $('.ixr_id');
+    const $roadType = $('.road_type');
     const type      = $type.val();
-    const roadNmbr  = $roadNmbr.val();
+    const isRoad    = $roadType.val() === 'road';
+    const roadNmbr  = isRoad ? $roadNmbr.val() : null;
     const year      = Number($year.val());
     const month     = Number($month.val());
     const date      = Number($date.val());
+    const ixrId     = isRoad ? null : $ixrId.val();
 
     let fromDt = "";
     let toDt   = "";
@@ -177,6 +191,7 @@ function searchStatisticsAmount() {
         roadNmbr : roadNmbr,
         fromDt   : fromDt,
         toDt     : toDt,
+        ixrId    : ixrId,
     }
 
     let height = '100%';
@@ -193,7 +208,7 @@ function searchStatisticsAmount() {
     }
     getDataAsync('/api/statistics/amount/' + type, "POST", param, null, (jsonData)=>{
         if (jsonData && jsonData.length > 0) {
-            _atrdNm = $(".road_nmbr option:checked").text();
+            _atrdNm = isRoad ? $(".road_nmbr option:checked").text() : $(".ixr_id option:checked").text();
             _type   = $('.type option:checked').text();
             _date   = dateValue;
             if (type === "mn") _date = dateValue.substring(0,6);
@@ -213,8 +228,7 @@ function searchStatisticsAmount() {
                     else {
                         value = '-';
                     }
-                    // if (value === '0' || !value) value = '-';
-                    // if (value === '0' || !value) value = '-';
+
                     str += `<td>${value}</td>`
                 }
                 str += `</tr>`
@@ -427,6 +441,7 @@ function excelDown(){
 const excelHandler = {
     getExcelFileName : function() {
         let front = "";
+
         if (_atrdNm) {
             front = _atrdNm + ' - ';
         }

+ 204 - 138
src/main/resources/static/js/traffic/traffic.js

@@ -445,28 +445,28 @@ function getAtrd() {
                     mobileStr += `<option value="${upHillId}_${downHillId}">${key}</option>`
                 }
             }
-            const $mobileSelect = $('.mobile-select');
-            $mobileSelect.append($(mobileStr));
-            $mobileSelect.on('change', function(){
-                const ids = $(this).val();
-                if (ids && ids !== '-') {
-                    const idArr = ids.split('_');
-                    atrdClickEvent(idArr[0], idArr[1]);
-                }
-                else {
-                    let array = _MarkerHandle.selectedAtrd;
-                    if (array.length > 0) {
-                        array.forEach((atrdId)=>{
-                            const atrdObj = _MarkerHandle.findMarkerObj('atrd', atrdId);
-                            if (atrdObj) {
-                                atrdObj.marker.setMap(null);
-                            }
-                        })
-                        _MarkerHandle.selectedAtrd = [];
-                        // getVertex();
-                    }
-                }
-            })
+            //const $mobileSelect = $('.mobile-select');
+            // $mobileSelect.append($(mobileStr));
+            // $mobileSelect.on('change', function(){
+            //     const ids = $(this).val();
+            //     if (ids && ids !== '-') {
+            //         const idArr = ids.split('_');
+            //         atrdClickEvent(idArr[0], idArr[1]);
+            //     }
+            //     else {
+            //         let array = _MarkerHandle.selectedAtrd;
+            //         if (array.length > 0) {
+            //             array.forEach((atrdId)=>{
+            //                 const atrdObj = _MarkerHandle.findMarkerObj('atrd', atrdId);
+            //                 if (atrdObj) {
+            //                     atrdObj.marker.setMap(null);
+            //                 }
+            //             })
+            //             _MarkerHandle.selectedAtrd = [];
+            //             // getVertex();
+            //         }
+            //     }
+            // })
         }
         else {
             listStr = emptyStr;
@@ -554,76 +554,130 @@ function getVertex() {
                 }
                 _MarkerHandle.markerMaps.get('traffic').markers.push(trafficObj);
             })
-            // if ($ul[0]) {
-            //     $ul.empty();
-            //     let uRoadObjArr = [];
-            //     let dRoadObjArr = [];
-            //     let upStr = '';
-            //     let downStr = '';
-            //     for (let ii= uRoadIdArr.length - 1; ii >= 0; ii--) {
-            //         const idx = trafficArr.findIndex(obj=> obj.roadway_id === uRoadIdArr[ii].road_id.toString());
-            //         if (idx >= 0) {
-            //             uRoadObjArr.push(trafficArr[idx])
-            //         }
-            //     }
-            //
-            //     uRoadObjArr.forEach((obj, idx)=>{
-            //         const {cmtr_grad_cd, strt_nm_node, end_nm_node, sped, trvl_hh, roadway_id} = obj;
-            //         const isEnd = (uRoadObjArr.length - 1) === idx;
-            //         upStr +=`<div class="traffic-list" onmouseover="showSelectLine('${roadway_id}', true, ${isEnd})" onmouseleave="showSelectLine('${roadway_id}', false, ${isEnd})">
-            //                     <div>
-            //                         <div class="up arrow ${cmtr_grad_cd}"></div>
-            //                     </div>
-            //                     <div>
-            //                         <div>${end_nm_node}</div>
-            //                         <div>${sped}km/h 약 ${trvl_hh}분</div>
-            //                     </div>
-            //                 </div>`
-            //         if (isEnd) {
-            //             upStr+= `<div style="margin-left: 35px; padding-top: 10px; padding-bottom: 10px;">${strt_nm_node}</div>`;
-            //         }
-            //     })
-            //
-            //     for (let ii= 0; ii < dRoadIdArr.length; ii++) {
-            //         const idx = trafficArr.findIndex(obj=> obj.roadway_id === dRoadIdArr[ii].road_id.toString());
-            //         if (idx >= 0) {
-            //             dRoadObjArr.push(trafficArr[idx]);
-            //         }
-            //     }
-            //
-            //     dRoadObjArr.forEach((obj, idx)=>{
-            //         const {sped, trvl_hh, cmtr_grad_cd, strt_nm_node, end_nm_node, roadway_id} = obj;
-            //         let isStart = idx === 0;
-            //         downStr +=`<div class="traffic-list" onmouseover="showSelectLine('${roadway_id}', true, ${isStart})" onmouseleave="showSelectLine('${roadway_id}', false, ${isStart})">
-            //                         <div>
-            //                             <div class="down arrow ${cmtr_grad_cd}"></div>
-            //                         </div>
-            //                         <div>
-            //                             <div>${strt_nm_node}</div>
-            //                             <div>${sped}km/h 약 ${trvl_hh}분</div>
-            //                         </div>
-            //                     </div>`
-            //         if (dRoadObjArr.length - 1 === idx) {
-            //             downStr += `<div style="margin-left: 35px; padding-top: 10px; padding-bottom: 10px;">${end_nm_node}</div>`
-            //         }
-            //     })
-            //     let str = `<li>
-            //                     <div>
-            //                             <div>
-            //                                 ${upStr}
-            //                             </div>
-            //                             <div>
-            //                                 ${downStr}
-            //                             </div>
-            //                     </div>
-            //                 </li>`;
-            //
-            //     $ul.html(str);
-            // }
+            if ($ul[0]) {
+                $ul.empty();
+                let uRoadObjArr = [];
+                let dRoadObjArr = [];
+                let upStr = '';
+                let downStr = '';
+                for (let ii= uRoadIdArr.length - 1; ii >= 0; ii--) {
+                    const idx = trafficArr.findIndex(obj=> obj.roadway_id === uRoadIdArr[ii].road_id.toString());
+                    if (idx >= 0) {
+                        uRoadObjArr.push(trafficArr[idx])
+                    }
+                }
+
+                uRoadObjArr.forEach((obj, idx)=>{
+                    const {cmtr_grad_cd, strt_nm_node, end_nm_node, sped, trvl_hh, roadway_id} = obj;
+                    const isEnd = (uRoadObjArr.length - 1) === idx;
+                    let addClass = "";
+                    if (_MarkerHandle.trafficObj && roadway_id === _MarkerHandle.trafficObj) {
+                        addClass = "on";
+                    }
+                    //upStr +=`<div class="traffic-list" onmouseover="showSelectLine('${roadway_id}', true, ${isEnd})" onmouseleave="showSelectLine('${roadway_id}', false, ${isEnd})">
+                    upStr +=`<div class="traffic-list ${addClass}" onclick="selectRoadPoint('${roadway_id}')">
+                                <div>
+                                    <div class="up arrow ${cmtr_grad_cd}"></div>
+                                </div>
+                                <div>
+                                    <div>${end_nm_node}</div>
+                                    <div>${sped}km/h 약 ${trvl_hh}분</div>
+                                </div>
+                            </div>`
+                    if (isEnd) {
+                        upStr+= `<div style="margin-left: 35px; padding-top: 10px; padding-bottom: 10px;">${strt_nm_node}</div>`;
+                    }
+                })
+
+                for (let ii= 0; ii < dRoadIdArr.length; ii++) {
+                    const idx = trafficArr.findIndex(obj=> obj.roadway_id === dRoadIdArr[ii].road_id.toString());
+                    if (idx >= 0) {
+                        dRoadObjArr.push(trafficArr[idx]);
+                    }
+                }
+
+                dRoadObjArr.forEach((obj, idx)=>{
+                    const {sped, trvl_hh, cmtr_grad_cd, strt_nm_node, end_nm_node, roadway_id} = obj;
+                    let isStart = idx === 0;
+                    // downStr +=`<div class="traffic-list" onmouseover="showSelectLine('${roadway_id}', true, ${isStart})" onmouseleave="showSelectLine('${roadway_id}', false, ${isStart})">
+                    let addClass = "";
+                    if (_MarkerHandle.trafficObj && roadway_id === _MarkerHandle.trafficObj) {
+                        addClass = "on";
+                    }
+                    downStr +=`<div class="traffic-list ${addClass}" onclick="selectRoadPoint('${roadway_id}')">
+                                    <div>
+                                        <div class="down arrow ${cmtr_grad_cd}"></div>
+                                    </div>
+                                    <div>
+                                        <div>${strt_nm_node}</div>
+                                        <div>${sped}km/h 약 ${trvl_hh}분</div>
+                                    </div>
+                                </div>`
+                    if (dRoadObjArr.length - 1 === idx) {
+                        downStr += `<div style="margin-left: 35px; padding-top: 10px; padding-bottom: 10px;">${end_nm_node}</div>`
+                    }
+                })
+                let str = `<li>
+                                <div>
+                                        <div>
+                                            ${upStr}
+                                        </div>
+                                        <div>
+                                            ${downStr}
+                                        </div>
+                                </div>
+                            </li>`;
+
+                $ul.html(str);
+            }
         }
     });
 }
+let timer = null;
+let infoWindow = null;
+function selectRoadPoint(id) {
+    if (timer) {
+        clearTimeout(timer);
+        if (infoWindow) {
+            infoWindow.setMap(null);
+        }
+    }
+    const trafficObj = _MarkerHandle.findMarkerObj('traffic', id);
+    _MarkerHandle.trafficObj = id;
+
+    const xCoordinateArr = trafficObj.obj.x_crdn.split(',');
+    const yCoordinateArr = trafficObj.obj.y_crdn.split(',');
+    const x = xCoordinateArr[Math.round(xCoordinateArr.length / 2)];
+    const y = yCoordinateArr[Math.round(yCoordinateArr.length / 2)];
+    let position    = getKakaoPosition(y, x);
 
+    _MapHandler.map.setCenter(position);
+    getVertex();
+
+    trafficObj.polyBackLine.setOptions({
+        strokeColor : '#FF00FF',
+    });
+
+
+    const imageSize     = new kakao.maps.Size(19, 27);
+    const imageOffset   = new kakao.maps.Point(8.5, 27);
+    const imageOption = {
+        offset: imageOffset,
+        alt   : trafficObj.NAME,
+    };
+
+    let markerImage = new kakao.maps.MarkerImage('/images/icon/select_position.png', imageSize, imageOption);
+    infoWindow = new kakao.maps.Marker({
+        position: position,
+        image: markerImage,
+        zIndex: 99999,
+    });
+    infoWindow.setMap(_MapHandler.map);
+    timer = setTimeout(()=>{
+        infoWindow.setMap(null);
+    }, 2000);
+
+}
 
 function showSelectLine(id, isShow, isEnd) {
     const trafficObj = _MarkerHandle.findMarkerObj('traffic', id);
@@ -633,7 +687,6 @@ function showSelectLine(id, isShow, isEnd) {
         const yCoordinateArr = trafficObj.obj.y_crdn.split(',');
         let position    = getKakaoPosition(yCoordinateArr[0], xCoordinateArr[0]);
         if (isEnd) {
-            console.log(isEnd);
             const lastYIdx = yCoordinateArr.length - 1;
             const lastXIdx = xCoordinateArr.length - 1;
             position = getKakaoPosition(yCoordinateArr[lastYIdx], xCoordinateArr[lastXIdx])
@@ -677,6 +730,9 @@ function showSelectLine(id, isShow, isEnd) {
  * @param AdownHillId 하행 ID
  */
 function atrdClickEvent(AupHillId, AdownHillId) {
+    if (_MarkerHandle.trafficObj) {
+        _MarkerHandle.trafficObj = null;
+    }
     if (! _MarkerHandle.findMarkerArr('atrd').length) {
         return alert('도로정보를 불러오는 중입니다.');
     }
@@ -833,6 +889,7 @@ class MarkerObj {
     setImage (type) {
         const imageLocation = this.image + type + '.png';
         const { imageSize, imageOption } = this.getImageOptions();
+
         let markerImage = new kakao.maps.MarkerImage(imageLocation, imageSize, imageOption);
         this.marker.setImage(markerImage);
     }
@@ -879,7 +936,7 @@ class MarkerObj {
             const ixrId = this.prop.ixr_id;
             const ixrLi = $('#intersection-' + ixrId);
             ixrLi.addClass('click');
-            $('.mobile-select').val(ixrId);
+            //$('.mobile-select').val(ixrId);
             moveToScroll(true, _MarkerHandle.findMarkerArr('intersection') , ixrId); // 클릭 시 리스트 이동
         }
 
@@ -969,19 +1026,20 @@ class MarkerObj {
             this.infoWindow.remove();
             this.infoWindow = null;
         }
-        const mobilSelect = $('.mobile-select');
+        //const mobilSelect = $('.mobile-select');
 
-        if (mobilSelect[0] && _MarkerHandle.isDrawList(this.type)) { // 리스트가 있는 경우
-            mobilSelect.val("-");
+        // if (mobilSelect[0] && _MarkerHandle.isDrawList(this.type)) { // 리스트가 있는 경우
+        if (_MarkerHandle.isDrawList(this.type)) { // 리스트가 있는 경우
             let selectedLi   = $("#" + this.type + "-" + this.ID);
 
             if (selectedLi.hasClass('click')) { // 선택 리스트가 있는지 여부
-                selectedLi.removeClass('click');
+               selectedLi.removeClass('click');
             }
         }
 
+
         if (this.type === 'intersectionCamera' && _MarkerHandle.isDrawList('intersection')) {
-            mobilSelect.val("-");
+            //mobilSelect.val("-");
             let selectedId = _MarkerHandle.selectedMarker.prop.ixr_id;
             let selectedLi   = $("#intersection-" + selectedId);
 
@@ -1117,6 +1175,10 @@ class TrafficObj {
             strokeWeight = 2;
             strokeWeightBack = 4;
         }
+
+        if (_MarkerHandle.trafficObj && this.ID === _MarkerHandle.trafficObj) {
+            strokeWeightBack += 2
+        }
         this.polyBackLine = new kakao.maps.Polyline({
             path: linePath, // 선을 구성하는 좌표배열 입니다
             strokeWeight: strokeWeightBack, // 선의 두께 입니다
@@ -1137,40 +1199,40 @@ class TrafficObj {
 
         this.setVisibleMarker(_MarkerHandle.isDraw('traffic'));
 
-        new kakao.maps.event.addListener(this.polyLine, 'mouseover', function (event) {
-
-            this.setOptions({strokeColor: '#0000FF'});
-            const iwContent =
-                `<div class="trafficPop">
-                            <div class="traffic-speed ${trafficObj.cmtr_grad_cd}">
-                                <span class="traffic-name">${_self.NAME}</span>
-                                <span class="traffic-speed-info border-back ${trafficObj.cmtr_grad_cd}">${trafficObj.grad_nm}</span>
-                            </div>
-                            <div class="traffic-info">
-                                <span>${trafficObj.strt_nm_node} → ${trafficObj.end_nm_node}</span>
-                                <br>
-                                <span>소요시간 : </span>
-                                <span class="${trafficObj.cmtr_grad_cd}">약 ${textFormat(trafficObj.trvl_hh)}분 </span>
-                                <span>&nbsp; 속도 : </span>
-                                <span class="${trafficObj.cmtr_grad_cd}">약 ${textFormat(trafficObj.sped)}km/h</span>
-                            </div>
-                         </div>`;
-            _self.infoWindow = new kakao.maps.CustomOverlay({
-                map: _MapHandler.map,
-                clickable: true,
-                position: event.latLng,
-                content: iwContent,
-                xAnchor: -0.1,
-                yAnchor: 1.1,
-                zIndex: 999
-            });
-
-        });
-
-        daum.maps.event.addListener(this.polyLine, 'mouseout', function () {
-            this.setOptions({strokeColor:  g_color.get(trafficObj.cmtr_grad_cd)});
-            if (_self.infoWindow != null) _self.infoWindow.setMap(null);
-        });
+        // new kakao.maps.event.addListener(this.polyLine, 'mouseover', function (event) {
+        //
+        //     this.setOptions({strokeColor: '#0000FF'});
+        //     const iwContent =
+        //         `<div class="trafficPop">
+        //                     <div class="traffic-speed ${trafficObj.cmtr_grad_cd}">
+        //                         <span class="traffic-name">${_self.NAME}</span>
+        //                         <span class="traffic-speed-info border-back ${trafficObj.cmtr_grad_cd}">${trafficObj.grad_nm}</span>
+        //                     </div>
+        //                     <div class="traffic-info">
+        //                         <span>${trafficObj.strt_nm_node} → ${trafficObj.end_nm_node}</span>
+        //                         <br>
+        //                         <span>소요시간 : </span>
+        //                         <span class="${trafficObj.cmtr_grad_cd}">약 ${textFormat(trafficObj.trvl_hh)}분 </span>
+        //                         <span>&nbsp; 속도 : </span>
+        //                         <span class="${trafficObj.cmtr_grad_cd}">약 ${textFormat(trafficObj.sped)}km/h</span>
+        //                     </div>
+        //                  </div>`;
+        //     _self.infoWindow = new kakao.maps.CustomOverlay({
+        //         map: _MapHandler.map,
+        //         clickable: true,
+        //         position: event.latLng,
+        //         content: iwContent,
+        //         xAnchor: -0.1,
+        //         yAnchor: 1.1,
+        //         zIndex: 999
+        //     });
+        //
+        // });
+        //
+        // daum.maps.event.addListener(this.polyLine, 'mouseout', function () {
+        //     this.setOptions({strokeColor:  g_color.get(trafficObj.cmtr_grad_cd)});
+        //     if (_self.infoWindow != null) _self.infoWindow.setMap(null);
+        // });
     }
 
     setVisibleMarker(isVisible) {
@@ -1645,7 +1707,7 @@ function moveToScroll(flag, array, id) {
         $('.left-list-area .list-content.list').animate({
             scrollTop : scrollTop + 'px'
         });
-        $('.mobile-select').val(id);
+        //$('.mobile-select').val(id);
     }
 }
 
@@ -1691,14 +1753,14 @@ function receiveMarkerData(jsonData, classType, type) {
         const listSection  = $('.left-list-area .list-content.list');
         listSection.empty();
         listSection.html(listStr);
-        const $mobileSelect = $('.mobile-select');
-        $mobileSelect.append($(mobileStr));
-        $mobileSelect.on('change', function(){
-            const id = $(this).val();
-            if (id && id !== "-") {
-                infoWindowEvent(type, id , 'click');
-            }
-        });
+        //const $mobileSelect = $('.mobile-select');
+        //$mobileSelect.append($(mobileStr));
+        // $mobileSelect.on('change', function(){
+        //     const id = $(this).val();
+        //     if (id && id !== "-") {
+        //         infoWindowEvent(type, id , 'click');
+        //     }
+        // });
     }
 
     if (selectIncidentId) {
@@ -1741,6 +1803,9 @@ function setInfoWindowPositionWidthDraggable(markerObj, type) {
         top = $('header').outerHeight() + $('.toggle-button').outerHeight() + 2;
         left = $('.left-list-area').outerWidth();
     }
+    if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
+        left = position[1];
+    }
     infoWindow.css({
         top : top + 'px',
         left : left + 'px',
@@ -1947,6 +2012,7 @@ class MarkerManager{
         this.typeArr    = typeArr;
         this.selectedMarker = null;
         this.selectedAtrd   = [];
+        this.trafficObj = null;
     }
 
     //마커 초기화

+ 2204 - 0
src/main/resources/static/js/traffic/traffic_new.js

@@ -0,0 +1,2204 @@
+let _size  = [48, 48, 48, 48, 40, 32, 24, 22, 20, 18, 18];
+let _AtrdMap = new Map();
+let selectIncidentId = null;
+let _MarkerArr = [];
+
+const vmsWHMap = new Map();
+vmsWHMap.set('VMC1', {width: 400, height: 64});
+vmsWHMap.set('VMC2', {width: 384, height: 64});
+vmsWHMap.set('VMC3', {width: 288, height: 160});
+vmsWHMap.set('VMC4', {width: 256, height: 192});
+
+//시설물 유형
+// const _FacilityArray = ['cctv', 'vms', 'intersection', 'incident', 'traffic', 'parking', 'intersectionCamera', 'atrd'];
+const _FacilityArray = ['cctv', 'vms', 'incident', 'traffic', 'parking', 'atrd'];
+
+const g_color = new Map();
+g_color.set('LTC0', '#888888;');
+g_color.set('LTC1', '#2fba2c;');
+g_color.set('LTC2', '#ffc500;');
+g_color.set('LTC3', '#ee0000;');
+
+const CCTV_DISPLAY_TIME = 30 * 1000; // 영상 표출 시간
+const VMS_DISPLAY_TIME  = 4 * 1000; // VMS 이미지 표출 시간
+const _TrafficListMap = new Map();
+let _MapHandler; //카카오 맵 핸들러
+let _Level = 6; //현재 줌레벨
+let _MarkerHandle;
+
+let _methodMap = new Map();
+_methodMap.set('cctv', getCctv);
+_methodMap.set('vms', getVms);
+_methodMap.set('incident', getIncident);
+// _methodMap.set('intersection', getIntersection);
+_methodMap.set('traffic', getAtrd);
+_methodMap.set('parking', getParking);
+
+$(()=>{
+    // Map 요소 ID로 생성
+    _MapHandler = new MapHandler('map');
+    _MapHandler.init();
+    _MarkerHandle = new MarkerManager([..._FacilityArray]);
+    _MarkerHandle.init();
+
+    // 시설물 유형 정보로 flag 값 세팅
+    if (_type) {
+        _MarkerHandle.setIsDraw(_type, true);
+        _MarkerHandle.setIsDrawList(_type, true);
+        _methodMap.get(_type)();
+    }
+
+    /**
+     * 범례 클릭 이벤트 및 색 변환
+     * */
+    $('.legend-bottom button').on('click', function (){
+        const target     = $(this);
+        const legendIcon = target.children().eq(0);
+        const isShow     = !legendIcon.hasClass('active');
+
+        const id = target.attr('id');
+        const type = id.replace('-legend', '');
+
+        let subType = null;
+        //
+        // if (type === 'intersection') {
+        //     subType = 'intersectionCamera';
+        // }
+
+        if (type === 'traffic') {
+            subType = 'atrd';
+            getVertex();
+        }
+        else {
+            const markerArr = _MarkerHandle.findMarkerArr(type);
+            if (markerArr && markerArr.length <= 0){
+                _methodMap.get(type)();
+            }
+        }
+
+        let classToggle = 'removeClass';
+        if (isShow === true) {
+            classToggle = 'addClass';
+        }
+
+        legendIcon[classToggle]('active');
+        target[classToggle]('active');
+        _MarkerHandle.showHideMarker(type, isShow);
+        if (subType) {
+            _MarkerHandle.showHideMarker(subType, isShow);
+        }
+    })
+
+    /**
+     * 범례 호버 색 변환
+     * */
+    $('.legend-bottom ul li').hover((event)=>{
+        const iconDiv = $(event.currentTarget).children().eq(0);
+        const childDiv = iconDiv.children().eq(0);
+        if (!iconDiv.hasClass('active')) {
+            childDiv.addClass('hover');
+        }
+        else {
+            if (childDiv.hasClass('hover')) {
+                childDiv.removeClass('hover');
+            }
+        }
+    },
+    (event)=>{
+        const iconDiv = $(event.currentTarget).children().eq(0);
+        const childDiv = iconDiv.children().eq(0);
+
+        if (!iconDiv.hasClass('active')) {
+            childDiv.removeClass('hover');
+        }
+        else {
+            if (childDiv.hasClass('hover')) {
+                childDiv.removeClass('hover');
+            }
+        }
+    });
+
+    /**
+     * 탭 호버 색 변환
+     * */
+    $('.left-list-area .list-tab > li').hover((event)=>{
+        const iconDiv  = $(event.currentTarget);
+        const childDiv = iconDiv.children().eq(0);
+        if (!iconDiv.hasClass('active')) {
+            childDiv.addClass('hover');
+        }
+        else {
+            if (childDiv.hasClass('hover')) {
+                childDiv.removeClass('hover');
+            }
+        }
+    },
+    (event)=>{
+        const iconDiv = $(event.currentTarget);
+        const childDiv = iconDiv.children().eq(0);
+        if (!iconDiv.hasClass('active')) {
+            childDiv.removeClass('hover');
+        }
+        else {
+            if (childDiv.hasClass('hover')) {
+                childDiv.removeClass('hover');
+            }
+        }
+    });
+
+    //
+    // $('.tab-title > div').on('click', function(){
+    //     const $spot = $('.list-content.spot');
+    //     const $list = $('.list-content.list');
+    //     let searchType = $('.tab-title > div.active').text();
+    //     if (searchType === $(this).text()) return;
+    //     $('.tab-title > div').toggleClass("active");
+    //     searchType = $(this).text();
+    //     let list;
+    //     let spot;
+    //     if (searchType === '지점 검색') {
+    //         list = 'none';
+    //         spot = 'block';
+    //         $spot.html("");
+    //     }
+    //     else {
+    //         list = 'block';
+    //         spot = 'none';
+    //         $('.list-content.list > li').css('display', 'block');
+    //     }
+    //
+    //     $('#search-box').val("");
+    //     $list.css('display', list);
+    //     $spot.css('display', spot);
+    //     if (_MarkerArr.length > 0) {
+    //         _MarkerArr.forEach((obj)=>{
+    //             obj.setMap(null);
+    //         });
+    //         _MarkerArr = [];
+    //     }
+    // })
+
+    //좌측 리스트 검색 이벤트 (리스트 명칭 또는 주소와 비교)
+    $('#search-box').on('keyup', function () {
+        const searchText = $(this).val();
+        const $list = $('.list-content.list > li');
+        if (!$list.length) return;
+        const $activeTab = $('.list-tab .active');
+        const id = $activeTab.attr('id');
+        let type = id.replace("-tab", "");
+        // if (type === 'intersection') type = 'intersectionCamera';
+
+        const markerArr = _MarkerHandle.findMarkerArr(type);
+        let showIdArr = [];
+        if (markerArr.length) {
+            showIdArr = markerArr.map((item)=>{
+                if (item.ADDR && item.ADDR.includes(searchText)) {
+                    // if (type === 'intersectionCamera') {
+                    //     return 'intersection-' + item.prop.ixr_id;
+                    // }
+                    return type + '-' + item.ID;
+                }
+            })
+        }
+
+        for (let ii = 0; ii < $list.length; ii++) {
+            const li = $list.eq(ii);
+            if ($(this).val().length === 0) {
+                li.css('display', 'block');
+            }
+            else {
+                let text = li.text().toLowerCase();
+                const liId = li.attr('id');
+                if (showIdArr.includes(liId)){
+                    li.css('display', 'block');
+                }
+                else if (text.includes(searchText.toLowerCase())) {
+                    li.css('display', 'block');
+                }
+                else {
+                    li.css('display', 'none');
+                }
+            }
+        }
+    })
+});
+
+/**
+ * 지점찾기 이벤트
+ * @param event
+ */
+function searchLocation(event) {
+    if (event && event.key !== 'Enter') {
+        return;
+    }
+    if (_MarkerHandle.selectedMarker) {
+        _MarkerHandle.selectedMarker.close();
+    }
+    $('.list-content.spot').empty();
+    const $search = $('.location-box input');
+    if (_MarkerArr.length > 0) {
+        _MarkerArr.forEach((obj)=>{
+            obj.setMap(null);
+        });
+        _MarkerArr = [];
+    }
+    const searchText = $search.val();
+    if (!searchText || !searchText.trim()) return;
+    const geoCoder = new kakao.maps.services.Geocoder();
+    geoCoder.addressSearch('경북 포항시 '+searchText, function(result, status){
+        if (status === 'OK' && result && result.length > 0) {
+            let str = "";
+            const setMap = new Map();
+
+            const bounds = new kakao.maps.LatLngBounds();
+            let minX;
+            let minY;
+            let maxX;
+            let maxY;
+
+            result.forEach((obj, idx)=>{
+                let addr = obj.address_name;
+                if (obj.x && obj.y) {
+                    const position = getKakaoPosition(obj.y, obj.x);
+                    let marker = new kakao.maps.Marker({
+                        position : position,
+                        title : addr ,
+                        content : addr
+                    });
+
+                    new kakao.maps.event.addListener(marker, 'click', function() {
+                        moveLocation(obj.x, obj.y, idx);
+                    })
+                    if (!minX) {
+                        minX = obj.x;
+                    }
+
+                    if (!maxX) {
+                        maxX = obj.x;
+                    }
+                    if (!minY) {
+                        minY = obj.y;
+                    }
+
+                    if (!maxY) {
+                        maxY = obj.y;
+                    }
+
+                    minX = minX <= obj.x ? minX : obj.x;
+                    maxX = maxX >= obj.x ? maxX : obj.x;
+                    minY = minY <= obj.y ? minY : obj.y;
+                    maxY = maxY >= obj.y ? maxY : obj.y;
+
+                    marker.setMap(_MapHandler.map);
+                    _MarkerArr.push(marker);
+                    setMap.set(addr, obj);
+
+                    str+= `<li id="spot-${idx}" onclick="moveLocation(${obj.x}, ${obj.y}, ${idx})">
+                                ${obj.address_name}
+                           </li>`;
+                }
+            });
+
+            if (minX && minY && maxX && maxY) {
+                bounds.extend(new kakao.maps.LatLng(minY, minX));
+                bounds.extend(new kakao.maps.LatLng(maxY, maxX));
+                _MapHandler.setBounds(bounds);
+                $('.list-content.spot').html(str);
+                getVertex();
+            }
+        }
+    }, {page : 1, size: 30});
+}
+
+/**
+ * 지점 클릭 위치 이동
+ * @param xCoordinate x좌표
+ * @param yCoordinate y좌표
+ * @param listIndex 지점 선택 인덱스
+ */
+function moveLocation(xCoordinate, yCoordinate, listIndex) {
+    if (_MarkerHandle.selectedMarker) {
+        _MarkerHandle.selectedMarker.close();
+    }
+    const position = getKakaoPosition(yCoordinate, xCoordinate);
+    _MapHandler.setCenter(position);
+    _MapHandler.setLevel(3);
+    $('.list-content.spot > li.click').removeClass('click');
+    $('#spot-' +listIndex).addClass('click');
+    getVertex();
+}
+
+/**
+ * cctv 데이터 가져오기
+ */
+function getCctv() {
+    getDataAsync('/api/traffic/cctv-list', 'POST', null, null, (cctvData)=>{
+        getDataAsync('/api/itcs/detail-list', 'POST', null, null, (itcsData)=> {
+            console.log(cctvData.length);
+            let cctvTotalData = [...cctvData];
+            console.log(itcsData.length);
+            if (itcsData && itcsData.length > 0) {
+                itcsData.forEach((itcs)=>{
+                    let obj = intersectionCameraTransferToCctv(itcs);
+                    cctvTotalData.push(obj);
+                })
+            }
+            receiveMarkerData(cctvTotalData, TbCCtvObj, 'cctv');
+        })
+    }, null);
+}
+
+function intersectionCameraTransferToCctv(itcs) {
+    return {
+        cctv_mngm_nmbr: itcs.cmra_id + '_' + itcs.drct_dvsn_cd,
+        cctv_ctlr_id: itcs.cmra_id + '_' + itcs.drct_dvsn_cd,
+        cctv_ctlr_ip: null,
+        cctv_ctlr_port: null,
+        cctv_capt_ip: null,
+        cctv_capt_port: null,
+        cctv_fibr_ip: null,
+        cctv_encd_ip: null,
+        strm_svr_ip: null,
+        strm_svr_port: null,
+        strm_sesn_nm: null,
+        x_crdn: itcs.cmra_x_crdn,
+        y_crdn: itcs.cmra_y_crdn,
+        istl_lctn_nm: itcs.drct_lctn,
+        istl_lctn_addr: itcs.istl_lctn_addr,
+        cctv_chnl: 0,
+        del_yn: "N",
+        frst_regr_nmbr: null,
+        frst_rgst_dt: null,
+        last_crpr_nmbr: null,
+        last_crct_dt: null,
+        link_id: null,
+        node_id: null,
+        cctv_id: null,
+        rely_port: null,
+        strm_rtsp_addr: null,
+        strm_rtmp_addr: null,
+        strm_http_addr: itcs.hmpg_cmra_url,
+        strm_stor_addr: null,
+        area_cd: null,
+        cctv_type: null,
+        cctv_sbst_imgn: null,
+        cctv_sbst_dspl_yn: null,
+        cctv_lc: null,
+        cmra_id: null,
+        cmra_pswd: null,
+        cmra_chnl: null,
+        cmra_modl: null,
+        full_strm_sesn_nm: null,
+        cmra_port: null,
+        hmpg_dspl_en: 1,
+        mac_addr: null,
+        serial_no: null,
+        tta_cnfn_yn: null
+    };
+}
+
+/**
+ * vms 데이터 가져오기
+ */
+function getVms() {
+    getDataAsync('/api/traffic/vms-list', 'POST', null, null, (jsonData)=>{
+        if (jsonData && jsonData.length > 0) {
+            jsonData.sort((a, b)=>{
+                return a.vms_nm > b.vms_nm ? 1 : a.vms_nm < b.vms_nm ? -1 : 0;
+            })
+            // receiveFacilityData(jsonData, TbVmsObj,'vms');
+        }
+        receiveMarkerData(jsonData, TbVmsObj, 'vms');
+    }, null);
+}
+
+/**
+ * 돌발정보
+ */
+function getIncident () {
+    getDataAsync('/api/traffic/incident-list', 'POST', null, null, (jsonData)=>{
+        receiveMarkerData(jsonData, TbIncdObj, 'incident');
+    }, null);
+}
+
+/**
+ * 스마트교차로
+ */
+function getIntersection() {
+    getDataAsync('/api/itcs/list', 'POST', null, null, (jsonData)=>{
+        if (jsonData && jsonData.length > 0) {
+            jsonData.sort((a, b)=>{
+                return a.ixr_nm > b.ixr_nm ? 1 : a.ixr_nm < b.ixr_nm ? -1 : 0;
+            });
+            let data = [];
+            jsonData.forEach((obj)=>{
+                if (obj.detail.length > 0) {
+                    data.push(obj);
+                }
+            })
+            receiveMarkerData(data, IntersectionObj, 'intersection');
+        }
+    }, null);
+}
+
+/**
+ * 주차정보
+ */
+function getParking() {
+    getDataAsync('/api/traffic/parking-list', 'POST', null, null, (jsonData)=>{
+        receiveMarkerData(jsonData, TbParkingObj, 'parking');
+    }, null);
+}
+
+/**
+ * 간선도로 정보
+ */
+function getAtrd() {
+    getDataAsync("/api/traffic/atrd-vertex-all", "POST", null, null, (jsonData)=>{
+        if (jsonData && jsonData.length > 0) {
+            _AtrdMap = new Map();
+            jsonData.forEach((obj)=>{
+                _TrafficListMap.set(obj.level, obj.list);
+                obj.list.forEach((atrd)=>{
+                    const key = atrd.atrd_id +'_' + atrd.drct_cd +'_' + obj.level;
+                    let atrdArr = _AtrdMap.get(key);
+                    if (!atrdArr) {
+                        _AtrdMap.set(key, []);
+                        let atrdTemp = {...atrd};
+                        atrdTemp.ID = key;
+                        const atrdObj = new TbAtrdObj(atrdTemp);
+                        atrdObj.init();
+                        _MarkerHandle.markerMaps.get('atrd').markers.push(atrdObj);
+                    }
+                    _AtrdMap.get(key).push(atrd);
+                });
+            });
+
+            getVertex();
+        }
+    }, null);
+
+    getDataAsync('/api/traffic/atrd-list', 'POST', null, null, (jsonData)=>{
+        let emptyStr = '<div class="empty">소통정보 리스트가 없습니다.</div>'
+        let listStr = "";
+        let mobileStr = "";
+
+        if (jsonData) {
+            const atrdList = Object.keys(jsonData).sort().reduce(
+                (newObj,key) => {
+                    newObj[key] = jsonData[key];
+                    return newObj;
+                },
+                {}
+            );
+
+            for (let key in atrdList){
+                let list = jsonData[key];
+                if (list && list.length === 2) {
+                    const upHill   = list[list.findIndex(obj => obj.drct_cd === '0')];
+                    const downHill = list[list.findIndex(obj => obj.drct_cd === '1')];
+                    const upHillId   = upHill.atrd_id;
+                    const downHillId = downHill.atrd_id;
+
+                    listStr += `<li id="atrd_${upHillId}_${downHillId}" onclick="atrdClickEvent('${upHillId}', '${downHillId}')">
+                                    ${key}
+                                </li><ul id="${upHillId}_${downHillId}"></ul>`
+                    mobileStr += `<option value="${upHillId}_${downHillId}">${key}</option>`
+                }
+            }
+            const $mobileSelect = $('.mobile-select');
+            $mobileSelect.append($(mobileStr));
+            $mobileSelect.on('change', function(){
+                const ids = $(this).val();
+                if (ids && ids !== '-') {
+                    const idArr = ids.split('_');
+                    atrdClickEvent(idArr[0], idArr[1]);
+                }
+                else {
+                    let array = _MarkerHandle.selectedAtrd;
+                    if (array.length > 0) {
+                        array.forEach((atrdId)=>{
+                            const atrdObj = _MarkerHandle.findMarkerObj('atrd', atrdId);
+                            if (atrdObj) {
+                                atrdObj.marker.setMap(null);
+                            }
+                        })
+                        _MarkerHandle.selectedAtrd = [];
+                        // getVertex();
+                    }
+                }
+            })
+        }
+        else {
+            listStr = emptyStr;
+        }
+        const listSection = $('.left-list-area .list-content.list');
+        listSection.empty();
+        listSection.html(listStr);
+    });
+
+}
+
+/**
+ * 소통정보 가져오기
+ */
+function getVertex() {
+    let level = _MapHandler.getLevel();
+    const bounds = _MapHandler.getBounds();
+    const swLatLng = bounds.getSouthWest();
+    const neLatLng = bounds.getNorthEast();
+    const data = {
+        levl : level,
+        swLat : swLatLng.getLat(),
+        neLat : neLatLng.getLat(),
+        swLng : swLatLng.getLng(),
+        neLng : neLatLng.getLng(),
+    }
+
+    getDataAsync('/api/traffic/vertex-list', 'POST', data, null, (jsonData)=>{
+        let markerArr = _MarkerHandle.findMarkerArr('traffic');
+        if (markerArr.length > 0) {
+            markerArr.forEach((obj) => {
+                obj.setVisibleMarker(false);
+                if (obj.infoWindow) {
+                    obj.infoWindow.setMap(null);
+                }
+            })
+
+            _MarkerHandle.findMarkerArr('traffic').markers = [];
+        }
+        if (jsonData && jsonData.length > 0) {
+            let upHill = null;
+            let dowHill = null;
+            let upHillId = null;
+            let downHillId = null;
+            let roadIdArr = [];
+            let uRoadIdArr = [];
+            let dRoadIdArr = [];
+            if (_MarkerHandle.selectedAtrd.length > 0 && _MarkerHandle.isDraw('traffic')) {
+                const selectedAtrd = _MarkerHandle.selectedAtrd;
+                upHill = _MarkerHandle.findMarkerObj('atrd', selectedAtrd[0]);
+                dowHill = _MarkerHandle.findMarkerObj('atrd', selectedAtrd[1]);
+                upHill.setImage(0);
+                dowHill.setImage(1);
+                upHill.marker.setMap(_MapHandler.map);
+                dowHill.marker.setMap(_MapHandler.map);
+                const upHillArr = _AtrdMap.get(selectedAtrd[0]);
+                const downHillArr = _AtrdMap.get(selectedAtrd[1]);
+                const roadArr = [...upHillArr, ...downHillArr];
+                upHillId = selectedAtrd[0].substring(0, selectedAtrd[0].indexOf('_'));
+                downHillId = selectedAtrd[1].substring(0, selectedAtrd[1].indexOf('_'));
+                roadArr.forEach((obj)=>{
+                    if (obj.atrd_id === upHillId) {
+                        uRoadIdArr.push(obj);
+                    }
+                    else {
+                        dRoadIdArr.push(obj);
+                    }
+                    roadIdArr.push(obj.road_id.toString());
+                });
+
+            }
+
+            const $ul = $(`#${upHillId}_${downHillId}`);
+
+            const trafficArr = [];
+            jsonData.forEach((obj)=>{
+                const trafficObj = new TrafficObj(obj);
+                if (roadIdArr.length > 0) {
+                   if (roadIdArr.includes(trafficObj.ID.toString())) {
+                        trafficObj.polyBackLine.setOptions({
+                            strokeColor : 'black',
+                        });
+                        trafficArr.push(obj);
+                    }
+                }
+                _MarkerHandle.markerMaps.get('traffic').markers.push(trafficObj);
+            })
+            if ($ul[0]) {
+                $ul.empty();
+                let uRoadObjArr = [];
+                let dRoadObjArr = [];
+                let upStr = '';
+                let downStr = '';
+                for (let ii= uRoadIdArr.length - 1; ii >= 0; ii--) {
+                    const idx = trafficArr.findIndex(obj=> obj.roadway_id === uRoadIdArr[ii].road_id.toString());
+                    if (idx >= 0) {
+                        uRoadObjArr.push(trafficArr[idx])
+                    }
+                }
+
+                uRoadObjArr.forEach((obj, idx)=>{
+                    const {cmtr_grad_cd, strt_nm_node, end_nm_node, sped, trvl_hh, roadway_id} = obj;
+                    const isEnd = (uRoadObjArr.length - 1) === idx;
+                    let addClass = "";
+                    if (_MarkerHandle.trafficObj && roadway_id === _MarkerHandle.trafficObj) {
+                        addClass = "on";
+                    }
+                    //upStr +=`<div class="traffic-list" onmouseover="showSelectLine('${roadway_id}', true, ${isEnd})" onmouseleave="showSelectLine('${roadway_id}', false, ${isEnd})">
+                    upStr +=`<div class="traffic-list ${addClass}" onclick="selectRoadPoint('${roadway_id}')">
+                                <div>
+                                    <div class="up arrow ${cmtr_grad_cd}"></div>
+                                </div>
+                                <div>
+                                    <div>${end_nm_node}</div>
+                                    <div>${sped}km/h 약 ${trvl_hh}분</div>
+                                </div>
+                            </div>`
+                    if (isEnd) {
+                        upStr+= `<div style="margin-left: 35px; padding-top: 10px; padding-bottom: 10px;">${strt_nm_node}</div>`;
+                    }
+                })
+
+                for (let ii= 0; ii < dRoadIdArr.length; ii++) {
+                    const idx = trafficArr.findIndex(obj=> obj.roadway_id === dRoadIdArr[ii].road_id.toString());
+                    if (idx >= 0) {
+                        dRoadObjArr.push(trafficArr[idx]);
+                    }
+                }
+
+                dRoadObjArr.forEach((obj, idx)=>{
+                    const {sped, trvl_hh, cmtr_grad_cd, strt_nm_node, end_nm_node, roadway_id} = obj;
+                    let isStart = idx === 0;
+                    // downStr +=`<div class="traffic-list" onmouseover="showSelectLine('${roadway_id}', true, ${isStart})" onmouseleave="showSelectLine('${roadway_id}', false, ${isStart})">
+                    let addClass = "";
+                    if (_MarkerHandle.trafficObj && roadway_id === _MarkerHandle.trafficObj) {
+                        addClass = "on";
+                    }
+                    downStr +=`<div class="traffic-list ${addClass}" onclick="selectRoadPoint('${roadway_id}')">
+                                    <div>
+                                        <div class="down arrow ${cmtr_grad_cd}"></div>
+                                    </div>
+                                    <div>
+                                        <div>${strt_nm_node}</div>
+                                        <div>${sped}km/h 약 ${trvl_hh}분</div>
+                                    </div>
+                                </div>`
+                    if (dRoadObjArr.length - 1 === idx) {
+                        downStr += `<div style="margin-left: 35px; padding-top: 10px; padding-bottom: 10px;">${end_nm_node}</div>`
+                    }
+                })
+                let str = `<li>
+                                <div>
+                                        <div>
+                                            ${upStr}
+                                        </div>
+                                        <div>
+                                            ${downStr}
+                                        </div>
+                                </div>
+                            </li>`;
+
+                $ul.html(str);
+            }
+        }
+    });
+}
+let timer = null;
+let infoWindow = null;
+function selectRoadPoint(id) {
+    if (timer) {
+        clearTimeout(timer);
+        if (infoWindow) {
+            infoWindow.setMap(null);
+        }
+    }
+    const trafficObj = _MarkerHandle.findMarkerObj('traffic', id);
+    _MarkerHandle.trafficObj = id;
+
+    const xCoordinateArr = trafficObj.obj.x_crdn.split(',');
+    const yCoordinateArr = trafficObj.obj.y_crdn.split(',');
+    const x = xCoordinateArr[Math.round(xCoordinateArr.length / 2)];
+    const y = yCoordinateArr[Math.round(yCoordinateArr.length / 2)];
+    let position    = getKakaoPosition(y, x);
+
+    _MapHandler.map.setCenter(position);
+    getVertex();
+
+    trafficObj.polyBackLine.setOptions({
+        strokeColor : '#FF00FF',
+    });
+
+
+    const imageSize     = new kakao.maps.Size(19, 27);
+    const imageOffset   = new kakao.maps.Point(8.5, 27);
+    const imageOption = {
+        offset: imageOffset,
+        alt   : trafficObj.NAME,
+    };
+
+    let markerImage = new kakao.maps.MarkerImage('/images/icon/select_position.png', imageSize, imageOption);
+    infoWindow = new kakao.maps.Marker({
+        position: position,
+        image: markerImage,
+        zIndex: 99999,
+    });
+    infoWindow.setMap(_MapHandler.map);
+    timer = setTimeout(()=>{
+        infoWindow.setMap(null);
+    }, 2000);
+
+}
+
+function showSelectLine(id, isShow, isEnd) {
+    const trafficObj = _MarkerHandle.findMarkerObj('traffic', id);
+    if (trafficObj.infoWindow) trafficObj.infoWindow.setMap(null);
+    if (isShow) {
+        const xCoordinateArr = trafficObj.obj.x_crdn.split(',');
+        const yCoordinateArr = trafficObj.obj.y_crdn.split(',');
+        let position    = getKakaoPosition(yCoordinateArr[0], xCoordinateArr[0]);
+        if (isEnd) {
+            const lastYIdx = yCoordinateArr.length - 1;
+            const lastXIdx = xCoordinateArr.length - 1;
+            position = getKakaoPosition(yCoordinateArr[lastYIdx], xCoordinateArr[lastXIdx])
+        }
+        const {cmtr_grad_cd, grad_nm, strt_nm_node, end_nm_node, trvl_hh, sped} = trafficObj.obj;
+        const iwContent =
+            `<div class="trafficPop">
+                            <div class="traffic-speed ${cmtr_grad_cd}">
+                                <span class="traffic-name">${trafficObj.NAME}</span>
+                                <span class="traffic-speed-info border-back ${cmtr_grad_cd}">${grad_nm}</span>
+                            </div>
+                            <div class="traffic-info">
+                                <span>${strt_nm_node} → ${end_nm_node}</span>
+                                <br>
+                                <span>소요시간 : </span>
+                                <span class="${cmtr_grad_cd}">약 ${textFormat(trvl_hh)}분 </span>
+                                <span>&nbsp; 속도 : </span>
+                                <span class="${cmtr_grad_cd}">약 ${textFormat(sped)}km/h</span>
+                            </div>
+                         </div>`;
+        trafficObj.infoWindow = new kakao.maps.CustomOverlay({
+            clickable: true,
+            position: position,
+            content: iwContent,
+            xAnchor: -0.05,
+            yAnchor: 1.1,
+            zIndex: 999
+        });
+
+        trafficObj.polyLine.setOptions({strokeColor: '#0000FF'});
+        trafficObj.infoWindow.setMap(_MapHandler.map);
+    }
+    else {
+        trafficObj.polyLine.setOptions({strokeColor: g_color.get(trafficObj.obj.cmtr_grad_cd)});
+    }
+}
+
+/**
+ * 간선도로 클릭 이벤트
+ * @param AupHillId 상행 ID
+ * @param AdownHillId 하행 ID
+ */
+function atrdClickEvent(AupHillId, AdownHillId) {
+    if (_MarkerHandle.trafficObj) {
+        _MarkerHandle.trafficObj = null;
+    }
+    if (! _MarkerHandle.findMarkerArr('atrd').length) {
+        return alert('도로정보를 불러오는 중입니다.');
+    }
+
+    if (_MarkerHandle.selectedAtrd.length > 0) {
+        _MarkerHandle.selectedAtrd.forEach((atrdId)=>{
+            _MarkerHandle.findMarkerObj('atrd', atrdId).marker.setMap(null);
+        })
+    }
+    _MarkerHandle.selectedAtrd = [];
+    const isDrawList = _MarkerHandle.isDrawList('traffic');
+    const $selectedLi = $('.left-list-area .list-content.list > li.click');
+    if (isDrawList && $selectedLi) {
+        $selectedLi.removeClass('click');
+    }
+
+    //상행, 하행 ID 가 있다면 실행
+    if (AupHillId && AdownHillId) {
+        const $selectLi = $('#atrd_' + AupHillId  + "_" + AdownHillId);
+        $selectLi.addClass('click');
+        $selectLi.focus();
+        let level = getAtrdLevel(_Level);
+        let upHill   = _MarkerHandle.findMarkerObj('atrd', AupHillId + '_0_' + level);
+        let downHill = _MarkerHandle.findMarkerObj('atrd', AdownHillId + '_1_' + level);
+
+        const bounds = new kakao.maps.LatLngBounds();
+        bounds.extend(new kakao.maps.LatLng(upHill.prop.y_crdn_min, upHill.prop.x_crdn_min));
+        bounds.extend(new kakao.maps.LatLng(upHill.prop.y_crdn_max, upHill.prop.x_crdn_max));
+        bounds.extend(new kakao.maps.LatLng(downHill.prop.y_crdn_min, downHill.prop.x_crdn_min));
+        bounds.extend(new kakao.maps.LatLng(downHill.prop.y_crdn_max, downHill.prop.x_crdn_max));
+        _MapHandler.setBounds(bounds);
+        let boundAfterLevel = getAtrdLevel(_Level);
+
+        _MarkerHandle.selectedAtrd = [AupHillId + '_0_' + boundAfterLevel, AdownHillId + '_1_' + boundAfterLevel];
+        getVertex();
+        if ($('.list-content > ul.on')[0]) {
+            $('.list-content > ul.on').removeClass('on');
+        }
+        $(`#${AupHillId}_${AdownHillId}`).addClass('on');
+    }
+}
+
+/**
+ * 간선도로 레벨 설정
+ * @param level 현재 레벨
+ * @returns {number} 데이터 레벨
+ */
+function getAtrdLevel(level) {
+    if (level >= 8) {
+        level = 7;
+    }
+    else if (level >= 6) {
+        level = 6;
+    }
+    return level;
+}
+
+/**
+ * 마커 객체
+ */
+class MarkerObj {
+    constructor({ID, NAME, X_CRDN, Y_CRDN, URL, TYPE, IMAGE, IMAGE_TYPE, DATA, ADDR}) {
+        this.ID         = ID;
+        this.NAME       = NAME;
+        this.X_CRDN     = X_CRDN;
+        this.Y_CRDN     = Y_CRDN;
+        this.URL        = URL;
+        this.type       = TYPE;
+        this.ADDR       = ADDR;
+        this.marker     = null;
+        this.infoWindow = null;
+        this.iwContent  = null;
+        this.isClick    = false;
+        this.image      = IMAGE;
+        this.imageType  = IMAGE_TYPE;
+        this.timer      = null;
+        this.phaseArray = [];
+        this.prop       = {...DATA};
+        this.size       = [48, 48, 48, 48, 40, 32, 24, 22, 20, 18, 18];
+        this.atrdSize   = [48, 48, 48, 48, 48, 48, 48, 24, 24, 24, 24];
+        this.imageOnOff = ['cctv', 'incident', 'vms', 'parking'];
+    }
+
+    //스마트교차로 카메라는 카메라 방향별로 이미지를 돌려야하므로 두개 만드는 방식이 다름
+    init () {
+        const position = getKakaoPosition(this.Y_CRDN, this.X_CRDN);
+        // if (this.type !== 'intersectionCamera') {
+            const imageLocation = this.image + this.imageType + '.png';
+            const { imageSize, imageOption } = this.getImageOptions();
+            let markerImage = new kakao.maps.MarkerImage(imageLocation, imageSize, imageOption);
+            this.marker = new kakao.maps.Marker({
+                position: position,
+                image: markerImage,
+                zIndex: 5,
+                title: this.NAME,
+            });
+
+            const _self = this;
+
+            //마커 클릭이벤트 등록
+            new kakao.maps.event.addListener(this.marker, 'click', function() {
+                _self.click();
+            });
+        // }
+        // else {
+        //     const content = $('<div id="camera_'+this.ID+'" title="'+this.NAME+'">');
+        //     const angle = Number(this.prop.cmra_angn);
+        //     content.css(
+        //         {
+        //             width: '48px',
+        //             height: '48px',
+        //             backgroundImage:'url(/images/icon/intersection-cctv.png)',
+        //             backgroundSize : '48px 48px',
+        //             backgroundRepeat: 'no-repeat',
+        //             backgroundPosition : 'center',
+        //             transform : 'rotate(' + angle +'deg)'
+        //         });
+        //
+        //     this.marker = new kakao.maps.CustomOverlay({
+        //         content: content[0],
+        //         position: position,
+        //         zindex: 15,
+        //     });
+        //
+        //     const linePath1 = [this.prop.start_x, this.prop.start_y]
+        //     const linePath2 = [this.prop.end_x, this.prop.end_y];
+        //     const color = ['#888888', '#15B337', '#15B337', '#15B337', '#FFAA00', '#FFAA00', '#EB260C', '#EB260C', '#EB260C'];
+        //     this.polyLine = new kakao.maps.Polyline({
+        //         path: [
+        //             getKakaoPosition(linePath1[1], linePath1[0]),
+        //             getKakaoPosition(linePath2[1], linePath2[0]),
+        //         ],
+        //         strokeWeight: 10,
+        //         strokeColor: color[this.prop.acrd_los],
+        //         strokeOpacity: 1,
+        //         strokeStyle: 'solid',
+        //         endArrow: true,
+        //         name : this.NAME,
+        //     });
+        //
+        //     if (_Level <= 2 && _MarkerHandle.isDraw('intersectionCamera')){
+        //         marker.setMap(_MapHandler.map);
+        //         this.polyLine.setMap(_MapHandler.map);
+        //     }
+        //
+        //     content.on('click', ()=> {
+        //         this.click();
+        //     });
+        // }
+
+    }
+
+    //레벨별 이미지 사이즈 변경
+    setImage (type) {
+        const imageLocation = this.image + type + '.png';
+        const { imageSize, imageOption } = this.getImageOptions();
+
+        let markerImage = new kakao.maps.MarkerImage(imageLocation, imageSize, imageOption);
+        this.marker.setImage(markerImage);
+    }
+
+    //현재 레벨별 이미지 Option 값
+    getImageOptions() {
+        let size = this.size[_Level];
+        if (this.isClick) {
+            size = size * 1.5;
+        }
+        let offsetX = size/2;
+        let offsetY = size/2;
+
+        if (this.type === 'atrd') {
+            size = this.atrdSize[_Level];
+            offsetX = size/2;
+            offsetY = size;
+        }
+
+        const imageSize     = new kakao.maps.Size(size, size);
+        const imageOffset   = new kakao.maps.Point(offsetX, offsetY);
+        const imageOption = {
+            offset: imageOffset,
+            alt   : this.NAME,
+        };
+        return {imageSize : imageSize, imageOption : imageOption};
+    }
+
+    //클릭 이벤트
+    async click() {
+        const selectObj = _MarkerHandle.selectedMarker;
+        if (selectObj) {
+            if (selectObj === this) {
+                // if (selectObj.type !== 'intersection' || (selectObj.type === 'intersection' && _Level <= 2)) {
+                    return;
+                // }
+            }
+
+            // 선택 마커 종료
+            selectObj.close();
+        }
+
+        // if (this.type === 'intersectionCamera' && _MarkerHandle.isDrawList('intersection')) {
+        //     const ixrId = this.prop.ixr_id;
+        //     const ixrLi = $('#intersection-' + ixrId);
+        //     ixrLi.addClass('click');
+        //     $('.mobile-select').val(ixrId);
+        //     moveToScroll(true, _MarkerHandle.findMarkerArr('intersection') , ixrId); // 클릭 시 리스트 이동
+        // }
+
+        _MarkerHandle.selectedMarker = this;
+
+        const center = getKakaoPosition(this.Y_CRDN, this.X_CRDN);
+        let level = _Level;
+        // if (this.type === 'intersection') {
+        //     level = 2;
+        // }
+
+        _MapHandler.setLevel(level);
+        _MapHandler.setCenter(center);
+
+        if (this.iwContent) {
+            this.infoWindow = $(this.iwContent);
+            $('body').append(this.infoWindow);
+            if (this.type === 'parking' && this.prop.prk_plce_nmbr) {
+                const parkData = await $.ajax({url: '/api/traffic/parking-live-info', method: 'post', data:{nmbr: this.prop.prk_plce_nmbr}});
+                if (parkData.remndr_prk_cmprt_co && parkData.remndr_prk_cmprt_co >= 0) {
+                    const remndrPrkCmprtCo = parkData.remndr_prk_cmprt_co + '대';
+                    $('.remndr_prk_cmprt_co').attr('title', '주차가능면수 : '+ remndrPrkCmprtCo);
+                    $('.remndr_prk_cmprt_co').html(remndrPrkCmprtCo);
+                }
+            }
+            let type = this.type;
+            // if (type === 'intersectionCamera') type = 'cctv';
+            setInfoWindowPositionWidthDraggable(this, type);
+        }
+
+        if (this.URL) {
+            this.videoEvent();
+        }
+
+        if (this.phaseArray) {
+            this.vmsEvent();
+        }
+
+        const selectedLi = $('#' + this.type + '-' + this.ID);
+
+        selectedLi.addClass('click');
+
+        if (_MarkerHandle.isDraw('traffic')) {
+            getVertex();
+        }
+
+        this.isClick = true;
+
+        if (this.imageOnOff.includes(this.type)) {
+            this.setImage('2');
+        }
+
+
+
+        if (_MarkerHandle.isDrawList(this.type)) {
+            const markerArr = _MarkerHandle.findMarkerArr(this.type);
+            moveToScroll(true, markerArr, this.ID); // 클릭 시 리스트 이동
+        }
+
+    }
+
+    //닫기 이벤트
+    close() {
+        if (this.timer) { // 인터벌이 있는 경우
+            clearInterval(this.timer);
+            clearTimeout(this.timer);
+            this.timer = null;
+        }
+
+        if (this.limitTimer) {
+            clearInterval(this.limitTimer);
+            this.limitTimer = null;
+        }
+
+        this.isClick = false;
+        if (this.imageOnOff.includes(this.type)) {// 클릭 이미지가 있는 경우
+            this.setImage('1');
+        }
+
+        let oldPlayer = document.getElementById("video-" + this.ID);
+        if (oldPlayer) { // video 태그가 있는 경우
+
+            videojs(oldPlayer).dispose();
+        }
+
+        if (this.infoWindow) { // 인포 윈도우 삭제
+            this.infoWindow.remove();
+            this.infoWindow = null;
+        }
+        const mobilSelect = $('.mobile-select');
+
+        if (mobilSelect[0] && _MarkerHandle.isDrawList(this.type)) { // 리스트가 있는 경우
+            mobilSelect.val("-");
+            let selectedLi   = $("#" + this.type + "-" + this.ID);
+
+            if (selectedLi.hasClass('click')) { // 선택 리스트가 있는지 여부
+                selectedLi.removeClass('click');
+            }
+        }
+
+        // if (this.type === 'intersectionCamera' && _MarkerHandle.isDrawList('intersection')) {
+        //     mobilSelect.val("-");
+        //     let selectedId = _MarkerHandle.selectedMarker.prop.ixr_id;
+        //     let selectedLi   = $("#intersection-" + selectedId);
+        //
+        //     if (selectedLi.hasClass('click')) { // 선택 리스트가 있는지 여부
+        //         selectedLi.removeClass('click');
+        //     }
+        // }
+
+
+        //선택 해제
+        _MarkerHandle.selectedMarker = null;
+    }
+
+    //영상 30초 제한 시간 보여주기
+    showLimitTime() {
+        const timer = $('.timer');
+        if (!this.video) {
+            timer.text('-');
+            return;
+        }
+
+        timer.text(30);
+
+        if (this.limitTimer) {
+            clearInterval(this.limitTimer)
+            this.limitTimer = null;
+        }
+        let cnt = 30;
+        this.limitTimer = setInterval(()=>{
+            if(cnt === 0) {
+                clearInterval(this.limitTimer);
+                this.limitTimer = null;
+                return;
+            }
+            $('.timer').text(--cnt);
+        }, 1000)
+    }
+
+    //CCTV 타이머
+    playTimer() {
+        if (this.timer) {
+            clearTimeout(this.timer);
+            this.timer = null;
+        }
+
+        this.timer = setTimeout(()=>{
+            if (this.video) {
+                this.video.pause();
+            }
+        }, CCTV_DISPLAY_TIME);
+    }
+
+    //비디오 생성 및 플레이
+    videoEvent() {
+        this.video = createVideoJs(this.ID, this.URL, this);
+
+        this.playTimer();
+
+        this.showLimitTime();
+
+        $('.continue-play').on('click', ()=>{
+            this.playTimer();
+            this.showLimitTime();
+
+            if (this.video) {
+                this.video.play();
+            }
+            else {
+                $('#video-error').css('display', 'none');
+                $('.cctv-info-window .content > div:nth-child(1)').append($('<video id="video-'+this.ID+'" class="video-js" style="width: 100%; height: 100%;"></video>'))
+                this.videoEvent();
+            }
+        });
+    }
+
+    //vms 이미지 표출 이벤트
+    vmsEvent() {
+        let cnt = 1;
+        if (this.phaseArray.length > 0) {
+            this.timer = setInterval(()=>{
+                if (cnt === this.phaseArray.length) {
+                    cnt = 0;
+                }
+                const activeImage = $('.vms-info-window .content img.active');
+                if (activeImage[0]) {
+                    activeImage.removeClass('active');
+                }
+                $("#phase-" + this.phaseArray[cnt]).addClass('active');
+                cnt++;
+            }, VMS_DISPLAY_TIME);
+        }
+    }
+}
+
+/**
+ * 소통정보 Object
+ */
+class TrafficObj {
+    constructor(obj) {
+        this.ID = obj.roadway_id;
+        this.NAME = obj.roadway_nm;
+        this.obj = obj;
+        this.infoWindow = null;
+        this.polyLine = null;
+        this.polyBackLine = null;
+        this.ADDR = obj.addr;
+        this.init();
+    }
+
+    init() {
+        const _self = this;
+        const trafficObj = this.obj;
+        const xArray = trafficObj.x_crdn.split(",");
+        const yArray = trafficObj.y_crdn.split(",");
+        const linePath = [];
+        for (let ii = 0; ii < xArray.length; ii++) {
+            const x_crdn = Number(xArray[ii]);
+            const y_crdn = Number(yArray[ii]);
+            const coordinates = getKakaoPosition(y_crdn, x_crdn);
+            linePath.push(coordinates);
+        }
+
+        let strokeWeight = 5;
+        let strokeWeightBack = 7;
+        const level = _Level;
+
+        if (level === 3) {
+            strokeWeightBack = 6;
+        } else if (level === 5 || level === 4) {
+            strokeWeight = 3;
+            strokeWeightBack = 5;
+        } else if (level >= 6) {
+            strokeWeight = 2;
+            strokeWeightBack = 4;
+        }
+
+        if (_MarkerHandle.trafficObj && this.ID === _MarkerHandle.trafficObj) {
+            strokeWeightBack += 2
+        }
+        this.polyBackLine = new kakao.maps.Polyline({
+            path: linePath, // 선을 구성하는 좌표배열 입니다
+            strokeWeight: strokeWeightBack, // 선의 두께 입니다
+            strokeColor: '#eeeeee', // 선의 색깔입니다
+            strokeOpacity: 1, // 선의 불투명도 입니다 1에서 0 사이의 값이며 0에 가까울수록 투명합니다
+            strokeStyle: 'solid', // 선의 스타일입니다
+            zIndex: 1
+        });
+        this.polyLine = new kakao.maps.Polyline({
+            path: linePath, // 선을 구성하는 좌표배열 입니다
+            strokeWeight: strokeWeight, // 선의 두께 입니다
+            strokeColor: g_color.get(trafficObj.cmtr_grad_cd), // 선의 색깔입니다
+            strokeOpacity: 1, // 선의 불투명도 입니다 1에서 0 사이의 값이며 0에 가까울수록 투명합니다
+            strokeStyle: 'solid', // 선의 스타일입니다
+            zIndex: 2
+        });
+
+
+        this.setVisibleMarker(_MarkerHandle.isDraw('traffic'));
+
+        // new kakao.maps.event.addListener(this.polyLine, 'mouseover', function (event) {
+        //
+        //     this.setOptions({strokeColor: '#0000FF'});
+        //     const iwContent =
+        //         `<div class="trafficPop">
+        //                     <div class="traffic-speed ${trafficObj.cmtr_grad_cd}">
+        //                         <span class="traffic-name">${_self.NAME}</span>
+        //                         <span class="traffic-speed-info border-back ${trafficObj.cmtr_grad_cd}">${trafficObj.grad_nm}</span>
+        //                     </div>
+        //                     <div class="traffic-info">
+        //                         <span>${trafficObj.strt_nm_node} → ${trafficObj.end_nm_node}</span>
+        //                         <br>
+        //                         <span>소요시간 : </span>
+        //                         <span class="${trafficObj.cmtr_grad_cd}">약 ${textFormat(trafficObj.trvl_hh)}분 </span>
+        //                         <span>&nbsp; 속도 : </span>
+        //                         <span class="${trafficObj.cmtr_grad_cd}">약 ${textFormat(trafficObj.sped)}km/h</span>
+        //                     </div>
+        //                  </div>`;
+        //     _self.infoWindow = new kakao.maps.CustomOverlay({
+        //         map: _MapHandler.map,
+        //         clickable: true,
+        //         position: event.latLng,
+        //         content: iwContent,
+        //         xAnchor: -0.1,
+        //         yAnchor: 1.1,
+        //         zIndex: 999
+        //     });
+        //
+        // });
+        //
+        // daum.maps.event.addListener(this.polyLine, 'mouseout', function () {
+        //     this.setOptions({strokeColor:  g_color.get(trafficObj.cmtr_grad_cd)});
+        //     if (_self.infoWindow != null) _self.infoWindow.setMap(null);
+        // });
+    }
+
+    setVisibleMarker(isVisible) {
+        const isShow = isVisible ? _MapHandler.map : null;
+        this.polyBackLine.setMap(isShow);
+        this.polyLine.setMap(isShow);
+    }
+}
+
+/**
+ * 간선도로 Object
+ */
+class TbAtrdObj extends MarkerObj{
+    constructor(obj) {
+        super({
+            ID : obj.ID,
+            NAME : obj.atrd_nm + ' ['+ obj.drct_nm + ']',
+            X_CRDN : obj.x_crdn_arr.split(",")[0],
+            Y_CRDN : obj.y_crdn_arr.split(",")[0],
+            URL    : null,
+            TYPE   : 'atrd',
+            DATA   : obj,
+            IMAGE_TYPE : obj.drct_cd,
+            IMAGE : '/images/icon/atrd'
+        })
+    }
+}
+
+/**
+ * CCTV Object
+ */
+class TbCCtvObj extends MarkerObj{
+    constructor(obj) {
+        super({
+            ID         : obj.cctv_mngm_nmbr,
+            NAME       : obj.istl_lctn_nm,
+            X_CRDN     : obj.x_crdn,
+            Y_CRDN     : obj.y_crdn,
+            URL        : obj.strm_http_addr,
+            TYPE       : 'cctv',
+            DATA       : obj,
+            IMAGE_TYPE : '1',
+            IMAGE      : '/images/icon/cctv',
+            ADDR       : obj.istl_lctn_addr,
+        });
+        this.iwContent = this.getIwContent();
+    }
+
+    getIwContent() {
+        const {ID, NAME, type} = this;
+        let iwContent =
+            `<div class="cctv-info-window">
+                        <div class="title">
+                            <div class="cctv-name-${ID}">${NAME}</div>
+                            <div onclick="infoWindowEvent('${type}', '${ID}', 'close')">X</div>
+                        </div>
+                        <div class="content">
+                            <div style="display: flex; align-items: center; justify-content: center;">
+                                <video id="video-${ID}" class="video-js" style="width: 100%; height: 100%;"></video>
+                                <img id="video-error" style="display: none;" src="/images/icon/error.png" alt="스트리밍 오류 이미지">
+                            </div>
+                            <div>
+                                <div>남은시간 : <span class="timer">30</span> 초</div>
+                                <div class="continue-play">계속재생</div>
+                            </div>
+                        </div>
+                    </div>`;
+//<!--                                <div>※ CCTV영상은 30초간 제공됩니다.</div>-->
+        return iwContent;
+    }
+}
+/**
+ * Parking Object
+ */
+class TbParkingObj extends MarkerObj{
+    constructor(obj) {
+        super({
+            ID          : obj.parking_id,
+            NAME        : obj.parking_nm,
+            X_CRDN      : obj.x_crdn,
+            Y_CRDN      : obj.y_crdn,
+            URL         : null,
+            TYPE        : 'parking',
+            DATA        : obj,
+            IMAGE_TYPE  : '1',
+            IMAGE       : '/images/icon/parking' + (obj.prk_plce_nmbr ? '1-' : '2-'),
+            ADDR        : obj.parking_addr,
+        })
+        this.iwContent = this.getIwContent();
+    }
+
+    getIwContent() {
+        const {ID, NAME, prop, X_CRDN, Y_CRDN} = this;
+        let iwContent =
+            `<div class="parking-info-window">
+                        <div class="title">
+                            <div class="parking-name-${ID}">${NAME}</div>
+                            <div onclick="infoWindowEvent('parking', '${ID}', 'close')"></div>
+                        </div>
+                        <div class="content">
+                            <div class="row">
+                                <div>주차면수</div>
+                                <div title="주차면수 : ${prop.parking_num} 대">${prop.parking_num} 대</div>
+                            </div>`;
+                if (prop.prk_plce_nmbr) {
+                    iwContent +=`<div class="row">
+                                    <div>주차가능면수</div>
+                                    <div class="remndr_prk_cmprt_co">- 대</div>
+                                </div>`;
+                }
+
+                iwContent +=`<div class="row">
+                                <div>구분</div>
+                                <div title="구분 : ${prop.parking_type_desc}">${prop.parking_type_desc}</div>
+                            </div>
+                            <div class="row">
+                                <div>기본요금</div>
+                                <div title="기본요금 : ${prop.parking_fee_type_desc}">${prop.parking_fee_type_desc}</div>
+                            </div>
+                             <div class="row">
+                                <div>운영시간</div>
+                                <div title="운영시간 : ${prop.parking_oper_time}">${prop.parking_oper_time}</div>
+                            </div>
+                            <div class="row">
+                                <div>주소</div>
+                                <div style="cursor:pointer;" title="주소 : ${prop.parking_addr}" onclick="openNavigationApp('${X_CRDN}','${Y_CRDN}','${prop.parking_addr}')">${prop.parking_addr}</div>
+                            </div>
+                        </div>
+                    </div>`;
+        return iwContent;
+    }
+}
+
+/**
+ * VMS Object
+ */
+class TbVmsObj extends MarkerObj{
+    constructor(obj) {
+        super({
+              ID            : obj.vms_ctlr_nmbr,
+              NAME          : obj.vms_nm,
+              X_CRDN        : obj.x_crdn,
+              Y_CRDN        : obj.y_crdn,
+              URL           : null,
+              TYPE          : 'vms',
+              IMAGE         : '/images/icon/vms',
+              IMAGE_TYPE    : '1',
+              DATA          : obj,
+              ADDR          : obj.istl_lctn_addr,
+        });
+        this.iwContent = this.getIwContent();
+    }
+
+    getIwContent() {
+        const {ID, NAME, prop} = this;
+        this.phaseArray = [];
+
+        let isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
+        const WH = vmsWHMap.get(prop.vms_type_cd);
+        let width = 256;
+        let height = 50;
+
+        if (WH) {
+            width = WH.width;
+            height = WH.height
+        }
+
+        const msg = prop.msg;
+        let content = "";
+        if (msg && msg.length > 0) {
+            const windowHeight = $(window).height();
+            if (isMobile || windowHeight < 900) {
+                width = WH.width/2;
+                height = WH.height/2;
+            }
+
+            for (let idx in msg) {
+                let msgObj = msg[idx];
+                let className = '';
+                if (idx === "0") {
+                    className = 'active'
+                }
+                content += `<img id="phase-${msgObj.phase}" class="${className}" style="width: ${width}px; height: ${height}px;" 
+                                         src="/api/traffic/vms-dspl-image/${ID}/${msgObj.phase}" alt="VMS 표출 이미지">`;
+                this.phaseArray.push(msgObj.phase);
+            }
+        }
+        else {
+            width = 256;
+            height = 50;
+            content += '표출 이미지 데이터가 없습니다.';
+        }
+        width = width +'px';
+        height = height + 'px';
+
+        let iwContent =
+            `<div class="vms-info-window" style="width: calc(${width} + 10px); height: calc(${height} + 50px);">
+                        <div class="title">
+                            <div class="vms-name-${ID}">${NAME}</div>
+                            <div onclick="infoWindowEvent('vms', ${ID}, 'close')">X</div>
+                        </div>
+                        <div class="content" style="width: ${width}; height: ${height};">
+                            ${content}
+                        </div>
+                    </div>`;
+        return iwContent;
+    }
+}
+
+/**
+ * 돌발정보 Object
+ */
+class TbIncdObj extends MarkerObj{
+    constructor(obj) {
+        super({
+            ID          : obj.incd_ocrr_id,
+            NAME        : obj.incd_titl,
+            X_CRDN      : obj.x_crdn,
+            Y_CRDN      : obj.y_crdn,
+            URL         : null,
+            TYPE        : 'incident',
+            IMAGE       : '/images/icon/incd',
+            IMAGE_TYPE  : '1',
+            DATA        : obj,
+            ADDR        : obj.road_nm,
+        });
+
+        this.iwContent = this.getIwContent();
+    }
+
+    getIwContent() {
+        const {ID, NAME, prop} = this;
+        let iwContent =
+            `<div class="incident-info-window">
+                <div class="title">
+                    <div class="incident-name-${ID}">${NAME}</div>
+                    <div onclick="infoWindowEvent('incident', '${ID}', 'close')">X</div>
+                </div>
+                <div class="content">
+                    <div>
+                        위치 : ${prop.road_nm}
+                    </div>
+                    <div>
+                        설명 : ${prop.incd_expl}
+                    </div>
+                    <div>
+                        기간 : ${prop.incd_strt_dt} ~ ${prop.incd_end_prar_dt}
+                    </div>
+                </div>
+            </div>`;
+        return iwContent;
+    }
+}
+
+/**
+ * 스마트 교차로 Object
+ */
+class IntersectionObj extends MarkerObj{
+    constructor(obj) {
+        super({
+            ID : obj.ixr_id,
+            NAME : obj.ixr_nm,
+            X_CRDN : obj.x_crdn,
+            Y_CRDN : obj.y_crdn,
+            URL    : null,
+            TYPE   : 'intersection',
+            DATA   : obj,
+            IMAGE_TYPE : "",
+            IMAGE : '/images/icon/intersection'
+        })
+    }
+}
+
+/**
+ * 스마트교차로 카메라 Object
+ */
+class IntersectionCameraObj extends MarkerObj{
+    constructor(obj) {
+        super(
+            {
+                ID          : obj.cmra_id + '_' + obj.drct_dvsn_cd,
+                NAME        : obj.drct_lctn,
+                X_CRDN      : obj.cmra_x_crdn,
+                Y_CRDN      : obj.cmra_y_crdn,
+                URL         : obj.hmpg_cmra_url,
+                TYPE        : 'intersectionCamera',
+                DATA        : obj,
+                IMAGE_TYPE  : "",
+                ADDR        : obj.istl_lctn,
+                IMAGE       : '/images/icon/intersection'
+            }
+        )
+        this.iwContent = this.getIwContent();
+    }
+
+    getIwContent() {
+        const {ID, NAME, type} = this;
+        let iwContent =
+            `<div class="cctv-info-window">
+                        <div class="title">
+                            <div class="cctv-name-${ID}">${NAME}</div>
+                            <div onclick="infoWindowEvent('${type}', '${ID}', 'close')">X</div>
+                        </div>
+                        <div class="content">
+                            <div>
+                                <video id="video-${ID}" class="video-js" playsinline style="width: 100%; height: 100%;"></video>
+                            </div>
+                            <div>
+                                <div>남은시간 : <span class="timer">30</span> 초</div>
+                                <div class="continue-play">계속재생</div>
+                            </div>
+                        </div>
+                    </div>`;
+
+        return iwContent;
+    }
+}
+
+/**
+ * 인포윈도우 이벤트
+ * @param type 시설물 유형
+ * @param id 요소 ID
+ * @param event 이벤트 종류 (click, close)
+ */
+function infoWindowEvent(type, id, event) {
+    const markerInfo = _MarkerHandle.findMarkerInfo(type); // 유형별 시설물 정보 찾기
+
+    // 시설물 마커들 중 id에 해당하는 객체 찾기
+    const markers = markerInfo.markers;
+    let idx = markers.findIndex(obj => obj.ID.toString() === id.toString());
+
+    // click, close 등 메서드로 등록한 것들도 키값으로 찾을 수 있음 obj.click() == obj['click']();
+    markers[idx][event]();
+}
+
+
+let isHide = false
+
+/**
+ * 좌측 목록 토글 이벤트
+ */
+function toggleEvent() {
+    const $listArea = $('.left-list-area');
+    const $toggleButton = $('.toggle-button');
+    const $locationButton = $('.location-btn');
+    const $locationBox = $('.location-box');
+
+    if (!isHide) {
+        $toggleButton.animate({
+            left: 0
+        }, 'slow');
+        $listArea.animate({
+            left: -$listArea.width()
+        }, 'slow');
+        $locationButton.animate({
+            left: 27
+        }, 'slow');
+        $locationBox.animate({
+            left: 27
+        }, 'slow');
+        $toggleButton.text('>');
+    }
+    else {
+        $toggleButton.animate({
+            left: $listArea.width()
+        }, 'slow');
+        $listArea .animate({
+            left: 0
+        }, 'slow');
+        $locationButton.animate({
+            left: $listArea.width() + 27
+        }, 'slow')
+        $locationBox.animate({
+            left: $listArea.width() + 27
+        }, 'slow')
+        $toggleButton.text('<');
+    }
+    isHide = !isHide;
+}
+
+
+/**
+ * 웹페이지 사이즈 별 위치 변경 이벤트
+ */
+window.addEventListener('resize', function(event) {
+    const $toggleButton   = $('.toggle-button');
+    const $locationButton = $('.location-btn');
+    const $listArea       = $('.left-list-area');
+    const $locationBox    = $('.location-box');
+
+    if ($(this).width() > 450) {
+        const left = $toggleButton.offset().left;
+        const listLeft = $listArea.offset().left;
+
+        if ($(this).width() <= 795) {
+            $locationButton.css('display', 'none');
+            $locationBox.css('display', 'none');
+            if(_MarkerArr.length > 0) {
+                _MarkerArr.forEach((obj)=>{
+                    obj.setMap(null);
+                });
+                _MarkerArr = [];
+            }
+            if ($locationButton.hasClass('on')) {
+                $locationButton.removeClass('on');
+            }
+            $('.list-content.spot').empty();
+            $('#location-text').val('');
+        }
+        else {
+            $locationButton.css('display', 'flex');
+        }
+        if ($(this).width() >= 920) {
+            if (left > 0 && left < 400) {
+                $toggleButton.css('left', 400);
+                $locationButton.css('left', 427);
+                $locationBox.css('left', 427);
+            }
+
+            if (listLeft > -400 && listLeft < 0) {
+                $listArea.css('left', -400);
+            }
+        }
+        else {
+            if (left > 0 && left > 275) {
+                $toggleButton.css('left', 275);
+                $locationButton.css('left', 302);
+                $locationBox.css('left', 302);
+            }
+
+            if (listLeft < -273) {
+                $listArea.css('left', -273);
+            }
+        }
+    }
+    else {
+        $locationBox.css('display', 'none');
+        $locationButton.removeClass('on');
+    }
+});
+
+
+/**
+ * 선택 리스트 스크롤 높이 반환 이벤트
+ * @param selectIndex 선택 인덱스
+ * @returns {number} 스크롤 높이
+ */
+function getScrollTop(selectIndex) {
+    let scrollTop = 0;
+    for (let ii=0; ii < selectIndex; ii++) {
+        let height = $('.left-list-area .list-content.list').children().eq(ii).css('height');
+        if (height) {
+            height = Number(height.replace('px', ''));
+            if (!isNaN(height)) {
+                scrollTop += height;
+            }
+        }
+    }
+    return scrollTop;
+}
+
+/**
+ * 좌측 리스트 선택 시 스크롤 변경 이벤트
+ * @param flag
+ * @param array
+ * @param id
+ */
+function moveToScroll(flag, array, id) {
+    if (flag) {
+        let selectIndex = array.findIndex((obj)=> obj.ID == id);
+        let scrollTop = getScrollTop(selectIndex);
+        $('.left-list-area .list-content.list').animate({
+            scrollTop : scrollTop + 'px'
+        });
+        $('.mobile-select').val(id);
+    }
+}
+
+/**
+ *
+ * @param jsonData Marker Info
+ * @param classType Marker Class
+ * @param type Marker type
+ */
+function receiveMarkerData(jsonData, classType, type) {
+    let listStr = "";
+    const title = $('.left-list-area .list-tab li.active > div:nth-child(2)').text();
+    let emptyStr = "<div class='empty'>" + title + " 리스트가 없습니다.</div>";
+    let mobileStr = "";
+    const { markers, isDraw, isDrawList } = _MarkerHandle.findMarkerInfo(type);
+    if (jsonData && jsonData.length) {
+        let markerArr = [];
+        jsonData.forEach((obj)=>{
+            const markerObj = new classType(obj);
+            markerObj.init();
+            listStr += `<li id="${markerObj.type}-${markerObj.ID}" onclick="infoWindowEvent('${type}', '${markerObj.ID}' , 'click')">${markerObj.NAME}</li>`
+            mobileStr += `<option value="${markerObj.ID}">${markerObj.NAME}</option>`;
+            markers.push(markerObj);
+            // if (type ==='intersection') {
+            //     markerObj.prop.detail.forEach((detail)=>{
+            //         const detailObj = new IntersectionCameraObj(detail);
+            //         detailObj.init();
+            //         _MarkerHandle.findMarkerInfo('intersectionCamera').markers.push(detailObj);
+            //     });
+            //     _MarkerHandle.setIsDraw('intersectionCamera', isDraw);
+            // }
+            markerArr.push(markerObj);
+        });
+
+        _MarkerHandle.setMarkers(type, markerArr);
+        _MarkerHandle.showHideMarker(type, isDraw);
+    }
+    else {
+        listStr = emptyStr;
+    }
+
+    if (isDrawList) {
+        const listSection  = $('.left-list-area .list-content.list');
+        listSection.empty();
+        listSection.html(listStr);
+        const $mobileSelect = $('.mobile-select');
+        $mobileSelect.append($(mobileStr));
+        $mobileSelect.on('change', function(){
+            const id = $(this).val();
+            if (id && id !== "-") {
+                infoWindowEvent(type, id , 'click');
+            }
+        });
+    }
+
+    if (selectIncidentId) {
+        infoWindowEvent('incident', selectIncidentId , 'click');
+    }
+}
+
+
+/**
+ * 맵 중앙 아이콘 위 위치 좌표
+ * @param infoWindow
+ * @returns {number[]}
+ */
+function getInfoWidowPosition(infoWindow) {
+    const map    = $('#map');
+    let mapHalfW = map.innerWidth() / 2;
+    let mapHalfH = map.innerHeight() / 2;
+    let mapTop   = map.offset().top;
+    let halfW    = infoWindow.innerWidth() / 2;
+    let height   = infoWindow.innerHeight();
+    let left     = mapHalfW - halfW;
+    let iconH    = _size[_Level];
+    let top = mapTop + mapHalfH - height - iconH - 10;
+    return [top, left];
+}
+
+/**
+ * Marker Drag Event
+ * @param markerObj Drag Marker Object
+ * @param type Marker Type
+ */
+function setInfoWindowPositionWidthDraggable(markerObj, type) {
+    const { infoWindow, ID } = markerObj;
+    const position = getInfoWidowPosition(infoWindow); //Current Marker Position
+    // console.log(infoWindow.width());
+    let top = position[0];
+    let left = position[1] + (infoWindow.width() / 2) + (markerObj.atrdSize[_Level] / 2);
+
+    if (markerObj.type === 'parking') {
+        top = $('header').outerHeight() + $('.toggle-button').outerHeight() + 2;
+        left = $('.left-list-area').outerWidth();
+    }
+    infoWindow.css({
+        top : top + 'px',
+        left : left + 'px',
+        position : 'absolute',
+        zIndex : 999,
+    });
+
+    //containment : 이동 반경 제한, handle : Drag Element
+    infoWindow.draggable({containment : 'body', handle: '.'+ type + '-name-' + ID});
+}
+
+
+/**
+ * Kakao Map 좌표 지정
+ */
+function getKakaoPosition(yCoordinate, xCoordinate) {
+    return new kakao.maps.LatLng(Number(yCoordinate), Number(xCoordinate));
+}
+
+/**
+ * videoJs 객체 생성
+ */
+function createVideoJs(id, url, obj) {
+    let video = videojs("video-" + id, {
+        sources: [
+            {
+                src: url,
+                type: "application/x-mpegURL",
+                crossorigin: "anonymous",
+            },
+        ],
+        responsive: false,
+        autoplay: true,
+        muted: true,
+        preload: "metadata",
+    });
+
+    //에러발생 시 대체 이미지 생성
+    video.on('error', function(){
+        if (this.error().code === 4) {
+            this.pause();
+            this.dispose();
+            $('#video-error').css('display', 'block');
+            clearInterval(obj.limitTimer);
+            clearTimeout(obj.timer);
+            $('.timer').text('-');
+            obj.video = null;
+        }
+    });
+
+    return video;
+}
+
+/**
+ * Map Handler Class
+ */
+class MapHandler {
+    constructor(id) {
+        this.selectedObj = null;
+        this.map = null;
+        this.mapElement = id;
+        this.atrd = [];
+    }
+
+    //Kakao Map 생성 및 이벤트 설정
+    init () {
+        const container = document.getElementById(this.mapElement); //지도를 담을 영역의 DOM 레퍼런스
+        const options = { //지도를 생성할 때 필요한 기본 옵션
+            center: getKakaoPosition(36.0191816, 129.3432983), //지도의 중심좌표.
+            level: _Level, //현재 레벨
+            maxLevel: 9, //최대 레벨
+            minLevel: 1, //최소 레벨
+            disableDoubleClickZoom: true, //더블 클릭 줌 방지
+        };
+        this.map = new kakao.maps.Map(container, options);
+        const mapTypeControl = new kakao.maps.MapTypeControl(); //지도, 스카이뷰 버튼 생성
+        this.map.addControl(mapTypeControl, kakao.maps.ControlPosition.TOPRIGHT); //지도, 스카이뷰 버튼 위치 설정
+        const zoomControl = new kakao.maps.ZoomControl(); //줌 버튼 생성
+        this.map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHT); // 줌 버튼 위치 설정
+
+        /**
+         * Map Zoom Level Change 이벤트 (이미지 사이즈, 스마트 교차로 이미지 토글, 소통정보 정보 변경)
+         */
+        kakao.maps.event.addListener(this.map, 'zoom_changed', function () {
+            _Level = this.getLevel();
+            const zoomChangeArray = ['cctv', 'vms', 'parking', 'incident'];
+            zoomChangeArray.forEach((type)=>{
+                const isDraw = _MarkerHandle.isDraw(type);
+
+                if (isDraw) {
+                    const markers = _MarkerHandle.findMarkerArr(type);
+                    if (markers && markers.length > 0) {
+                        markers.forEach((obj)=>{
+                            let imageType = "";
+                            if (obj.imageType === '1') {
+                                imageType = obj.isClick ? '2' :'1';
+                            }
+
+                            // if (obj.type === "intersectionCamera") {
+                            //     let isIxrCm = _Level > 2 ? null : _MapHandler.map;
+                            //     obj.marker.setMap(isIxrCm);
+                            //     obj.polyLine.setMap(isIxrCm);
+                            // }
+                            // else {
+                                obj.setImage(imageType);
+                            // }
+
+                            // if (obj.type === 'intersection') {
+                            //     let isIxr = _Level <= 2 ? null : _MapHandler.map;
+                            //     obj.marker.setMap(isIxr);
+                            // }
+
+                        })
+                    }
+                }
+            });
+
+            if (_MarkerHandle.isDraw('traffic')) {
+                const selectedAtrd = _MarkerHandle.selectedAtrd;
+                if (selectedAtrd.length > 0) {
+                    let level = getAtrdLevel(_Level);
+                    let array = [];
+                    selectedAtrd.forEach((atrdId)=>{
+                        const atrdObj = _MarkerHandle.findMarkerObj('atrd', atrdId);
+                        if (atrdObj) {
+                            atrdObj.marker.setMap(null);
+                            let changeId = atrdId.slice(0, -1);
+                            array.push(changeId + level);
+                        }
+                    });
+                    _MarkerHandle.selectedAtrd = array;
+                }
+                getVertex();
+            }
+        });
+
+        /**
+         * 맵 드래그 이벤트
+         * 좌표 데이터가의 양이 많으므로 이동 할때마다 영역별 소통정보를 새로 그려줌
+         */
+        kakao.maps.event.addListener(this.map, 'dragend', function() {
+            if (_MarkerHandle.isDraw('traffic')) {
+                getVertex();
+            }
+        });
+
+        // kakao.maps.event.addListener(this.map, 'bounds_changed', function() {
+        //     if (_MarkerHandle.isDraw('traffic')) {
+        //         getVertex();
+        //     }
+        // });
+
+        /**
+         * 맵 클릭 이벤트
+         */
+        kakao.maps.event.addListener(this.map, 'click', function (mouseEvent) {
+            /** 클릭 이벤트 필요 시 설정 **/
+        });
+    }
+
+    getLevel() {
+        return this.map.getLevel();
+    }
+
+    setLevel(level) {
+        if (!isNaN(level)) {
+            this.map.setLevel(level);
+        }
+        else {
+            console.error("Invalid Value Error - MapHandler.setValue(), Please Check The Zoom Level Value : ", level);
+        }
+    }
+
+    setCenter(position) {
+        if (position) {
+            this.map.setCenter(position);
+        }
+        else {
+            console.error("Invalid Values Error - MapHandler.setCenter, Value is Empty : ", position);
+        }
+    }
+
+    getBounds() {
+        return this.map.getBounds();
+    }
+
+    setBounds(bounds) {
+        if (bounds) {
+            this.map.setBounds(bounds);
+        }
+        else {
+            console.error("Invalid Values Error - MapHandler.setBound, Value is Empty : ", bounds);
+        }
+    }
+
+}
+
+/**
+ * 마커 관리 Class
+ */
+class MarkerManager{
+    constructor(typeArr) {
+        this.markerMaps = new Map();
+        this.typeArr    = typeArr;
+        this.selectedMarker = null;
+        this.selectedAtrd   = [];
+        this.trafficObj = null;
+    }
+
+    //마커 초기화
+    init() {
+        if (this.typeArr && this.typeArr.length > 0) {
+            for (let type of this.typeArr) {
+                this.markerMaps.set(type, {
+                    isDraw     : false, //마커 지도 이미지
+                    isDrawList : false, //좌측 교통정보 리스트
+                    markers    : [],    //마커 빈값 초기화
+                    type       : type,  //교통정보 종류
+                });
+            }
+        }
+    }
+
+    //마커 세팅
+    setMarkers(type, array) {
+        if (array && array.length) {
+            this.markerMaps.get(type).markers = [...array];
+        }
+    }
+
+    //유형별 마커 관리 객체 찾기
+    findMarkerInfo(type) {
+        return this.markerMaps.get(type);
+    }
+
+    //유형별 화면 표출 여부
+    isDraw(type) {
+        return this.findMarkerInfo(type).isDraw;
+    }
+
+    //유형별 리스트 표출 여부
+    isDrawList(type) {
+        return this.findMarkerInfo(type).isDrawList;
+    }
+
+    //유형별 화면 표출 여부 세팅
+    setIsDraw(type, isDraw) {
+        this.findMarkerInfo(type).isDraw = isDraw;
+    }
+
+    //유형별 리스트 표출 여부 세팅
+    setIsDrawList(type, isDraw) {
+        this.findMarkerInfo(type).isDrawList = isDraw;
+    }
+
+    //유형별 마커 Array 찾기
+    findMarkerArr(type) {
+        return this.findMarkerInfo(type).markers;
+    }
+
+    //유형별 개별 마커 찾기
+    findMarkerObj(type, id) {
+        const markerArr = this.findMarkerArr(type);
+        let markerObj = null;
+        if (markerArr && markerArr.length > 0) {
+            let idx = markerArr.findIndex(obj => obj.ID === id);
+            if (!isNaN(idx) && markerArr[idx]) {
+                markerObj = markerArr[idx];
+            }
+        }
+        return markerObj;
+    }
+
+    //유형별 마커 보이기 / 감추기
+    showHideMarker(type, isShow) {
+        const markerArr = this.findMarkerArr(type);
+
+        if (markerArr && markerArr.length > 0) {
+            const map = _MapHandler.map;
+            for (let markerObj of markerArr) {
+                let visible = null
+
+                //스마트 교차로의 경우 2레벨 이하일때 카메라 위치, 그 외 교차로 위치를 보여준다.
+                switch (type) {
+                    // case 'intersectionCamera':
+                    //     if (isShow && _Level <= 2) visible = map;
+                    //     break;
+                    // case 'intersection':
+                    //     if (isShow && _Level > 2) visible = map;
+                    //     break;
+                    case 'atrd': //소통 정보 시 선택된 도로가 있는지 확인 후 진행
+                        if (this.selectedAtrd.length > 0) {
+                            if (this.selectedAtrd.includes(markerObj.ID.toString()) && isShow) {
+                                visible = map;
+                            }
+                        }
+                        break;
+                    default : //그 외 보이기 감추기
+                        visible = isShow ? map : null;
+                        break;
+                }
+
+                //마커, 폴리 라인을 맵에 보이기/감추기
+                if (markerObj.polyBackLine){
+                    markerObj.polyBackLine.setMap(visible);
+                }
+                if (markerObj.polyLine) {
+                    markerObj.polyLine.setMap(visible);
+                }
+                if (markerObj.marker) {
+                    markerObj.marker.setMap(visible);
+                    // if (type !== 'intersectionCamera') {
+                    //     markerObj.setImage(markerObj.imageType);
+                    // }
+                }
+            }
+        }
+
+        //현재 보이기/감추기 상태값 설정
+        this.setIsDraw(type, isShow);
+    }
+}
+
+/**
+ * 지점 검색 리스트 보이기 / 감추기
+ */
+function toggleLocationBox() {
+    const $locationBox = $('.location-box');
+    if (_MarkerArr.length > 0) {
+        _MarkerArr.forEach((obj)=>{
+            obj.setMap(null);
+        });
+        _MarkerArr = [];
+    }
+    $('.location-box input').val('');
+    $('.list-content.spot').empty();
+    $locationBox.toggle();
+    $('.location-btn').toggleClass('on');
+}

+ 6 - 0
src/main/resources/templates/admin/conn-statistics.html

@@ -19,6 +19,12 @@
         <h2 class="admin-header">접속자 통계</h2>
         <div class="admin-content">
             <div class="title">
+                <select>
+                    <option value="dd" selected>일별</option>
+                    <option value="yy">최근 6개월</option>
+                    <option value="mn">월별</option>
+                    <option value="yy">년별</option>
+                </select>
                 <input id="date" type="text" placeholder="날짜를 선택해주세요.">
                 <label for="date" style="display: none"></label>
                 <div th:class="button" onclick="searchStat()">조회</div>

+ 53 - 33
src/main/resources/templates/center/center.html

@@ -17,42 +17,62 @@
         <div class="content1">
             <div>
                 <h2>설립 목적</h2>
-                <p>
-                    포항시의 각종 교통정보를 연계 및 제공하여 시민들이 더욱 편리하게 교통시설을 이용하고 교통 서비스의 질적 개선을 도모하고자 설립되었습니다.
-                </p>
+                <div class="box">
+                    <p>
+                        포항시의 각종 교통정보를 연계 및 제공하여 시민들이 더욱 편리하게 교통시설을 이용하고 교통 서비스의 질적 개선을 도모하고자 설립되었습니다.
+                    </p>
+                </div>
                 <h2>역할</h2>
-                <p>포항시 교통정보센터는 교통정보 수집·가공·분석·정보제공 등 교통 운영 및 관리에 대한 업무를 수행하며, 교통정보시스템의 효율적인 운영과 유지관리,
-                    현장 시설물과 센터 시설물의 예방정비 및 유지보수 업무를 담당하고 있습니다.</p>
-            </div>
-            <div>
-                <img src="/images/background/bg_center.jpg" alt="센터 이미지">
+                <div class="box">
+                    <p>포항시 교통정보센터는 교통정보 수집·가공·분석·정보제공 등 교통 운영 및 관리에 대한 업무를 수행하며, 교통정보시스템의 효율적인 운영과 유지관리,
+                        현장 시설물과 센터 시설물의 예방정비 및 유지보수 업무를 담당하고 있습니다.</p>
+                </div>
+                <div class="img-box">
+                    <img src="/images/background/center_img1.jpg" alt="포항시 지능형교통시스템(ITS) 조감도">
+                </div>
             </div>
         </div>
-        <div class="content2">
-            <table>
-                <thead>
-                    <tr>
-                        <th colspan="3">시스템안내</th>
-                    </tr>
-                </thead>
-                <tbody>
-                    <tr class="bt-dbl">
-                        <td>도시교통정보시스템</td>
-                        <td>신호제어시스템</td>
-                        <td>스마트교차로시스템</td>
-                    </tr>
-                    <tr>
-                        <td>도로교통전광판, 교통관제 CCTV 운영</td>
-                        <td>포항시 신호 제어기 운영·관리</td>
-                        <td>교통량 수집분석을 통한 교차로 최적 주기 산출</td>
-                    </tr>
-                    <tr>
-                        <td>차량탑재장치(OBE), 노변기지국(RSE) 간 교통정보 수집</td>
-                        <td>교차로 신호 주기 조정 및 운영</td>
-                        <td>대기행렬 분석 및 소통정보 제공</td>
-                    </tr>
-                </tbody>
-            </table>
+<!--        <div class="content2">-->
+<!--            <table>-->
+<!--                <thead>-->
+<!--                    <tr>-->
+<!--                        <th colspan="3">시스템안내</th>-->
+<!--                    </tr>-->
+<!--                </thead>-->
+<!--                <tbody>-->
+<!--                    <tr class="bt-dbl">-->
+<!--                        <td>도시교통정보시스템</td>-->
+<!--                        <td>스마트교차로시스템</td>-->
+<!--                    </tr>-->
+<!--                    <tr>-->
+<!--                        <td>도로교통전광판, 교통관제 CCTV 운영</td>-->
+<!--                        <td>교통량 수집분석을 통한 교차로 최적 주기 산출</td>-->
+<!--                    </tr>-->
+<!--                    <tr>-->
+<!--                        <td>차량탑재장치(OBE), 노변기지국(RSE) 간 교통정보 수집</td>-->
+<!--                        <td>대기행렬 분석 및 소통정보 제공</td>-->
+<!--                    </tr>-->
+<!--                </tbody>-->
+<!--            </table>-->
+<!--        </div>-->
+        <div class="content1">
+            <h2>시스템안내</h2>
+            <div class="content3">
+                <div>
+                    <div>도시교통정보시스템</div>
+                    <div>
+                        <div>도로교통전광판, 교통관제 CCTV 운영</div>
+                        <div>차량탑재장치(OBE), 노변기지국(RSE) 간 교통정보 수집</div>
+                    </div>
+                </div>
+                <div>
+                    <div>스마트교차로시스템</div>
+                    <div>
+                        <div>교통량 수집분석을 통한 교차로 최적 주기 산출</div>
+                        <div>대기행렬 분석 및 소통정보 제공</div>
+                    </div>
+                </div>
+            </div>
         </div>
     </div>
 </div>

+ 161 - 138
src/main/resources/templates/include/header.html

@@ -7,170 +7,193 @@
             <p>포항시교통정보센터</p>
         </div>
         <div class="top-menu">
-            <div class="top-menu-cont" th:classappend="${selected == 'traffic'} ? ' on' : ''">
-                <div></div>
-                <a th:href="@{/trafficMap/cctvMap}">교통정보</a>
+            <div class="top-menu-cont traffic">
+                <a>교통정보</a>
             </div>
-            <div class="top-menu-cont" th:classappend="${selected == 'statistics'} ? ' on' : ''">
-                <div></div>
-                <a th:href="@{/statistics/traffic01}">교통통계</a>
+            <div class="top-menu-cont statistics">
+                <a>교통통계</a>
             </div>
-            <div class="top-menu-cont" th:classappend="${selected == 'parking'} ? ' on' : ''">
-                <div></div>
-                <a th:href="@{/trafficMap/parking}">주차정보</a>
+            <div class="top-menu-cont parking">
+                <a>주차정보</a>
             </div>
-            <div class="top-menu-cont">
-                <div></div>
-                <a href="javascript:openBusSite(0)">버스정보</a>
-                <img src="/images/icon/direction.png" alt="새 창 열기">
+            <div class="top-menu-cont bus">
+                <a>버스정보</a>
             </div>
-            <span tabindex="0" id="menu" onclick="menuOpen()">
-                    <img src="/images/icon/menu.png" alt="menu">
-                </span>
-            <div id="menu-modal">
-                <div class="modal-close-box">
-                    <div class="modal-close" onclick="closeModal('header #menu-modal')"></div>
-                </div>
-                <div class="modal-container">
+            <div class="top-menu-cont notice">
+                <a>공지사항</a>
+            </div>
+            <div class="top-menu-cont center">
+                <a>교통정보센터</a>
+            </div>
+            <div class="down-menu">
+                <div>
                     <div>
-                        <img class="modal-icon" src="/images/icon/menu_icon1.png" alt="교통정보 아이콘">
-                        <div class="modal-icon-title">
-                            <h4>교통정보</h4>
+                        <div>
+                            <div class="traffic-child" th:onclick="movePath('/trafficMap/cctvMap')">교통CCTV</div>
+                            <div class="traffic-child" th:onclick="movePath('/trafficMap/intersections')">스마트교차로</div>
+                            <div class="traffic-child" th:onclick="movePath('/trafficMap/vms')">도로전광판</div>
+                            <div class="traffic-child" th:onclick="movePath('/trafficMap/incidents')">돌발정보</div>
+                            <div class="traffic-child" th:onclick="movePath('/trafficMap/realtimetraffic')">소통정보</div>
                         </div>
-                        <div class="sub-number">
-                            <h1>01</h1>
+                        <div>
+                            <div class="statistics-child" th:onclick="movePath('/statistics/traffic01')">교통량통계</div>
+                            <div class="statistics-child" th:onclick="movePath('/statistics/traffic02')">소통통계</div>
+                            <div class="statistics-child" th:onclick="movePath('/statistics/congestion')">정체통계</div>
                         </div>
-                        <p th:onclick="movePath('/trafficMap/cctvMap')">교통CCTV</p>
-                        <p th:onclick="movePath('/trafficMap/intersections')">스마트교차로</p>
-                        <p th:onclick="movePath('/trafficMap/vms')">도로전광판</p>
-                        <p th:onclick="movePath('/trafficMap/incidents')">돌발정보</p>
-                        <p th:onclick="movePath('/trafficMap/realtimetraffic')">소통정보</p>
-                    </div>
-                    <div>
-                        <img class="modal-icon" src="/images/icon/menu_icon2.png" alt="교통통계 아이콘">
-                        <div class="modal-icon-title">
-                            <h4>교통통계</h4>
+                        <div>
+                            <div class="parking-child" th:onclick="movePath('/trafficMap/parking')">주차정보</div>
                         </div>
-                        <div class="sub-number">
-                            <h1>02</h1>
+                        <div>
+                            <div class="bus-child" th:onclick="openBusSite(0)">노선</div>
+                            <div class="bus-child" th:onclick="openBusSite(1)">정류소</div>
+                            <div class="bus-child" th:onclick="openBusSite(2)">요금</div>
                         </div>
-                        <p th:onclick="movePath('/statistics/traffic01')">교통량통계</p>
-                        <p th:onclick="movePath('/statistics/traffic02')">소통통계</p>
-                        <p th:onclick="movePath('/statistics/congestion')">정체통계</p>
-                    </div>
-                    <div>
-                        <img class="modal-icon" src="/images/icon/parking.png" alt="주차정보 아이콘">
-                        <div class="modal-icon-title">
-                            <h4>주차정보</h4>
+                        <div>
+                            <div class="notice-child" th:onclick="movePath('/notice/list')">공지사항</div>
                         </div>
-                        <div class="sub-number">
-                            <h1>03</h1>
+                        <div>
+                            <div class="center-child" th:onclick="movePath('/center/center')">센터소개</div>
+                            <div class="center-child" th:onclick="movePath('/center/way')">오시는길</div>
                         </div>
-                        <p onclick="movePath('/trafficMap/parking')">
-                            주차정보
-                        </p>
                     </div>
-                    <div>
-                        <img class="modal-icon" src="/images/icon/menu_icon3.png" alt="버스정보 아이콘">
-                        <div class="modal-icon-title">
-                            <h4>버스정보</h4>
+                </div>
+            </div>
+            <div class="three-line-menu">
+                <span tabindex="0" id="menu" onclick="menuOpen()">
+                     <img src="/images/icon/menu.png" alt="menu">
+                </span>
+                <div id="menu-modal">
+                    <div class="modal-close-box">
+                        <div class="modal-close" onclick="closeModal('header #menu-modal')"></div>
+                    </div>
+                    <div class="modal-container">
+                        <div>
+                            <img class="modal-icon" src="/images/icon/menu_icon1.png" alt="교통정보 아이콘">
+                            <div class="modal-icon-title">
+                                <h4>교통정보</h4>
+                            </div>
+                            <div class="sub-number">
+                                <h1>01</h1>
+                            </div>
+                            <p onclick="movePath('/trafficMap/cctvMap')">교통CCTV</p>
+                            <p onclick="movePath('/trafficMap/intersections')">스마트교차로</p>
+                            <p onclick="movePath('/trafficMap/vms')">도로전광판</p>
+                            <p onclick="movePath('/trafficMap/incidents')">돌발정보</p>
+                            <p onclick="movePath('/trafficMap/realtimetraffic')">소통정보</p>
                         </div>
-                        <div class="sub-number">
-                            <h1>04</h1>
+                        <div>
+                            <img class="modal-icon" src="/images/icon/menu_icon2.png" alt="교통통계 아이콘">
+                            <div class="modal-icon-title">
+                                <h4>교통통계</h4>
+                            </div>
+                            <div class="sub-number">
+                                <h1>02</h1>
+                            </div>
+                            <p onclick="movePath('/statistics/traffic01')">교통량통계</p>
+                            <p onclick="movePath('/statistics/traffic02')">소통통계</p>
+                            <p onclick="movePath('/statistics/congestion')">정체통계</p>
                         </div>
-                        <p onclick="openBusSite(0)">
-                            노선
-                            <img class="modal-direction" src="/images/icon/direction.png" alt="외부사이트 이미지">
-                        </p>
-                        <p onclick="openBusSite(1)">
-                            정류소
-                            <img class="modal-direction" src="/images/icon/direction.png" alt="외부사이트 이미지">
-                        </p>
-                        <p onclick="openBusSite(2)">
-                            요금
-                            <img class="modal-direction" src="/images/icon/direction.png" alt="외부사이트 이미지">
-                        </p>
-                    </div>
-                    <div>
-                        <img class="modal-icon" src="/images/icon/menu_icon4.png" alt="공지사항 아이콘">
-                        <div class="modal-icon-title">
-                            <h4>공지사항</h4>
+                        <div>
+                            <img class="modal-icon" src="/images/icon/parking.png" alt="주차정보 아이콘">
+                            <div class="modal-icon-title">
+                                <h4>주차정보</h4>
+                            </div>
+                            <div class="sub-number">
+                                <h1>03</h1>
+                            </div>
+                            <p onclick="movePath('/trafficMap/parking')">
+                                주차정보
+                            </p>
                         </div>
-                        <div class="sub-number">
-                            <h1>05</h1>
+                        <div>
+                            <img class="modal-icon" src="/images/icon/menu_icon3.png" alt="버스정보 아이콘">
+                            <div class="modal-icon-title">
+                                <h4>버스정보</h4>
+                            </div>
+                            <div class="sub-number">
+                                <h1>04</h1>
+                            </div>
+                            <p onclick="openBusSite(0)">
+                                노선
+                                <img class="modal-direction" src="/images/icon/direction.png" alt="외부사이트 이미지">
+                            </p>
+                            <p onclick="openBusSite(1)">
+                                정류소
+                                <img class="modal-direction" src="/images/icon/direction.png" alt="외부사이트 이미지">
+                            </p>
+                            <p onclick="openBusSite(2)">
+                                요금
+                                <img class="modal-direction" src="/images/icon/direction.png" alt="외부사이트 이미지">
+                            </p>
                         </div>
-                        <p onclick="movePath('/notice/list')">
-                            공지사항
-                        </p>
-                    </div>
-                    <div>
-                        <img class="modal-icon" src="/images/icon/menu_icon5.png" alt="교통정보센터 아이콘">
-                        <div class="modal-icon-title">
-                            <h4>교통정보센터</h4>
+                        <div>
+                            <img class="modal-icon" src="/images/icon/menu_icon4.png" alt="공지사항 아이콘">
+                            <div class="modal-icon-title">
+                                <h4>공지사항</h4>
+                            </div>
+                            <div class="sub-number">
+                                <h1>05</h1>
+                            </div>
+                            <p onclick="movePath('/notice/list')">
+                                공지사항
+                            </p>
                         </div>
-                        <div class="sub-number">
-                            <h1>06</h1>
+                        <div>
+                            <img class="modal-icon" src="/images/icon/menu_icon5.png" alt="교통정보센터 아이콘">
+                            <div class="modal-icon-title">
+                                <h4>교통정보센터</h4>
+                            </div>
+                            <div class="sub-number">
+                                <h1>06</h1>
+                            </div>
+                            <p onclick="movePath('/center/center')">
+                                센터소개
+                            </p>
+                            <p onclick="movePath('/center/way')">
+                                오시는길
+                            </p>
                         </div>
-                        <p onclick="movePath('/center/center')">
-                            센터소개
-                        </p>
-                        <p onclick="movePath('/center/way')">
-                            오시는길
-                        </p>
                     </div>
                 </div>
             </div>
         </div>
     </div>
-    <th:block th:if="${selected == 'statistics'}" th:include="include/statistics-menu"></th:block>
-    <th:block th:if="${selected == 'center'}" th:include="include/center-menu"></th:block>
 </header>
 
 <script th:inline="javascript">
+    const menus =  $('.top-menu');
+    const downMenu = $('.down-menu');
+    const downMenuChildren = $('.down-menu > div > div > div > div');
+    const selected = [[${selected}]];
+    const mobileMenu = $('.three-line-menu');
+    menus.on('mouseleave', function(){
+        if (mobileMenu.css('display') === 'none') {
+            downMenu.css('display', 'none');
+        }
+    })
+
+    menus.on('mouseover', function() {
+        if (mobileMenu.css('display') === 'none') {
+           downMenu.css('display', 'flex');
+        }
+    });
+
+    downMenuChildren.on('mouseleave', function() {
+        let className = $(this).attr('class');
+        if (!className.includes(selected)) {
+            className = className.replace('-child', '');
+            $('.' + className).removeClass('on');
+        }
+    });
+
+    downMenuChildren.on('mouseover', function() {
+        let className = $(this).attr('class');
+        if (!className.includes(selected)) {
+            className = className.replace('-child', '');
+            $('.' + className).addClass('on');
+        }
+    });
 
-    // function menuOpen() {
-    //     let isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
-    //     let display = 'flex';
-    //     if (isMobile) {
-    //         if ($(window).width() <= 450) {
-    //             display = 'block';
-    //         }
-    //     }
-    //     $('header #menu-modal').css('display', display);
-    // }
-    //
-    // window.addEventListener(`resize`, function () {
-    //     const display = $('header #menu-modal').css('display');
-    //     if (display === 'flex' || display === 'block') {
-    //         if ($(window).width() <= 450) {
-    //             $('header #menu-modal').css('display', 'block');
-    //         } else {
-    //             $('header #menu-modal').css('display', 'flex');
-    //         }
-    //     }
-    //
-    // });
-    //
-    // function closeModal(target) {
-    //     $(target).css('display', 'none');
-    // }
-    //
-    // function openBusSite(ord) {
-    //     const urlArr = [
-    //         'https://www.pohang.go.kr/bis/busRoute.do',
-    //         'https://www.pohang.go.kr/bis/busStation.do',
-    //         'https://www.pohang.go.kr/dept/contents.do?mid=0505020100',
-    //     ];
-    //     window.open(urlArr[ord], '_blank', "noopener noreferrer")
-    // }
-    //
-    // function movePath(uri) {
-    //     window.location.href = uri;
-    // }
-    //
-    // function openModal(target) {
-    //     $(target).css('display', 'flex');
-    //     $(target + " .content").scrollTop(0);
-    // }
+    $('.' + selected).addClass('on');
 
 </script>

+ 35 - 65
src/main/resources/templates/main/main.html

@@ -15,86 +15,51 @@
         <th:block th:include="include/header.html"></th:block>
         <div class="mainWrap">
             <div class="main">
-                <div class="main-menu">
-                    <div class="top">
-                        <div title="포항시 교통 CCTV 영상을 확인할 수 있습니다." class="first-bg" th:onclick="movePath('/trafficMap/cctvMap')">
-                            <div>교통CCTV</div>
-                        </div>
-                        <div title="포항시 실시간 도로 소통 정보를 확인할 수 있습니다." class="other-bg road-bg" th:onclick="movePath('/trafficMap/realtimetraffic')">
-                            <div>실시간소통정보</div>
-                        </div>
-                        <div title="포항시 교통 통계를 제공합니다." class="other-bg stat-bg" th:onclick="movePath('/statistics/traffic01')">
-                            <div>교통통계</div>
-                        </div>
-                        <div title="포항시 주차정보를 제공합니다." class="other-bg parking-bg" th:onclick="movePath('/trafficMap/parking')">
-                            <div>주차정보</div>
-                        </div>
-                        <div title="포항시 버스정보시스템 홈페이지로 이동합니다.(새 창 열기)" class="other-bg bus-bg" th:onclick="openBusSite(0)">
-                            <div>
-                                버스정보시스템
-                                <img class="bus-direction" src="/images/icon/direction.png" alt="새 창 열기">
-                            </div>
-                        </div>
-                    </div>
-                    <div class="mid">
-                        <div class="notice">
-                            <div>
-                                <img src="/images/icon/alarm.png" alt="공지사항 아이콘">
-                                <span>공지사항</span>
-                                <a href="/notice/list">+</a>
-                            </div>
-                            <div class="content">
-                                <div th:if="${notice != null && notice.size() > 0}" th:onclick="moveNoticeView([[${item.getBoardNo()}]], false)" th:each="item, i:${notice}">
+                <div class="mid">
+                    <div class="notice">
+                        <div>
+                            <img src="/images/icon/alarm.png" alt="공지사항 아이콘">
+                            <span>공지사항</span>
+                            <div class="content" onload="slideNotice(this)">
+                                <div style="display: flex;" th:if="${notice != null && notice.size() > 0}" th:onclick="moveNoticeView([[${item.getBoardNo()}]], false)" th:each="item, i:${notice}">
                                     <div th:title="${item.getBSubject()}" th:text="${item.getBSubject()}"></div>
-                                    <span th:text="${item.getRegDate()}"></span>
+                                    <div style="margin-left: auto; width: 100px;" th:text="${item.getRegDate()}"></div>
                                 </div>
                                 <div th:if="${notice == null} or ${notice.size() == 0}">데이터가 없습니다.</div>
                             </div>
+                            <a href="/notice/list">+</a>
                         </div>
-                        <div class="incd">
-                            <div>
-                                <img src="/images/icon/incd.png" alt="돌발정보 아이콘">
-                                <span>돌발정보</span>
-                                <a href="/trafficMap/incidents">+</a>
+                    </div>
+                </div>
+                <div class="main-menu">
+                    <div class="top">
+                        <div>
+                            <div title="포항시 교통 CCTV 영상을 확인할 수 있습니다." class="first-bg" th:onclick="movePath('/trafficMap/cctvMap')">
+                                <div>교통CCTV</div>
                             </div>
-                            <div class="incd-content">
-                                <div th:if="${incident != null and incident.size() > 0}" th:each="item, i:${incident}">
-                                    <div th:onclick="movePath([['/trafficMap/incidents/'+ ${item.getIncdOcrrId()}]])" th:title="${item.getIncdTitl()}" th:text="${item.getIncdTitl()}"></div>
-                                    <span th:text="${item.getAgoMinutes()}"></span>
-                                </div>
-                                <div th:if="${incident == null} or ${incident.size() == 0}">데이터가 없습니다.</div>
+                        </div>
+                        <div>
+                            <div title="포항시 실시간 도로 소통 정보를 확인할 수 있습니다." class="other-bg road-bg" th:onclick="movePath('/trafficMap/realtimetraffic')">
+                                <div>실시간소통정보</div>
                             </div>
                         </div>
-<!--                        <div class="center" onclick="movePath('/center/center')">-->
-<!--                            <div>교통정보센터 소개 ></div>-->
-<!--                        </div>-->
-                    </div>
-                    <div class="bottom">
-                        <div class="center" onclick="movePath('/center/center')">
-                            <div>교통정보센터 소개 ></div>
+                        <div>
+                            <div title="포항시 주차정보를 제공합니다." class="other-bg parking-bg" th:onclick="movePath('/trafficMap/parking')">
+                                <div>주차정보</div>
+                            </div>
                         </div>
                         <div>
-                            <div><img src="/images/icon/video.png" alt="동영상 아이콘">홍보 동영상</div>
-                            <div class="video-box">
-                                <video style="object-fit: fill; width: 100%; height: 100%;" controls preload="metadata" th:src="@{/video/gumi.mp4}" type="video/mp4"></video>
+                            <div title="포항시 교통 통계를 제공합니다." class="other-bg stat-bg" th:onclick="movePath('/statistics/traffic01')">
+                                <div>교통통계</div>
                             </div>
                         </div>
-                    </div>
-<!--                    <div class="bottom">-->
-<!--                        <div class="first-box">-->
-<!--                            <h1>POHANG</h1>-->
-<!--                            <div>포항시교통정보센터 홍보 영상입니다.</div>-->
-<!--                        </div>-->
-<!--                        <div>-->
+<!--                        <div title="포항시 버스정보시스템 홈페이지로 이동합니다.(새 창 열기)" class="other-bg bus-bg" th:onclick="openBusSite(0)">-->
 <!--                            <div>-->
-<!--                                <video id="video" muted playsinline controls>-->
-<!--                                    <source src="/video/video.mp4" preload="metadata" type="video/mp4">-->
-<!--                                    이 문장은 여러분의 브라우저가 video 태그를 지원하지 않을 때 화면에 표시됩니다!-->
-<!--                                </video>-->
+<!--                                버스정보시스템-->
+<!--                                <img class="bus-direction" src="/images/icon/direction.png" alt="새 창 열기">-->
 <!--                            </div>-->
 <!--                        </div>-->
-<!--                    </div>-->
-
+                    </div>
                 </div>
             </div>
         </div>
@@ -148,4 +113,9 @@
         }
     }
 
+    function slideNotice(el) {
+        // $(el).children().css('display', 'none');
+        //
+    }
+
 </script>

+ 151 - 0
src/main/resources/templates/main/main_org.html

@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th=http://www.thymeleaf.org>
+    <head>
+        <meta charset="utf-8"/>
+        <meta name="viewport" content="width=device-width,initial-scale=1"/>
+        <meta name="description" content="포항시 교통정보센터입니다"/>
+        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
+        <title>포항시 교통정보센터</title>
+        <script th:src="@{/js/videojs/video-7.10.2.js}"></script>
+        <link rel="stylesheet" th:href="@{/css/video-7.10.2.css}">
+        <th:block th:include="include/head.html"></th:block>
+        <link rel="stylesheet" th:href="@{/css/main.css}">
+    </head>
+    <body id="body">
+        <th:block th:include="include/header.html"></th:block>
+        <div class="mainWrap">
+            <div class="main">
+                <div class="main-menu">
+                    <div class="top">
+                        <div title="포항시 교통 CCTV 영상을 확인할 수 있습니다." class="first-bg" th:onclick="movePath('/trafficMap/cctvMap')">
+                            <div>교통CCTV</div>
+                        </div>
+                        <div title="포항시 실시간 도로 소통 정보를 확인할 수 있습니다." class="other-bg road-bg" th:onclick="movePath('/trafficMap/realtimetraffic')">
+                            <div>실시간소통정보</div>
+                        </div>
+                        <div title="포항시 교통 통계를 제공합니다." class="other-bg stat-bg" th:onclick="movePath('/statistics/traffic01')">
+                            <div>교통통계</div>
+                        </div>
+                        <div title="포항시 주차정보를 제공합니다." class="other-bg parking-bg" th:onclick="movePath('/trafficMap/parking')">
+                            <div>주차정보</div>
+                        </div>
+                        <div title="포항시 버스정보시스템 홈페이지로 이동합니다.(새 창 열기)" class="other-bg bus-bg" th:onclick="openBusSite(0)">
+                            <div>
+                                버스정보시스템
+                                <img class="bus-direction" src="/images/icon/direction.png" alt="새 창 열기">
+                            </div>
+                        </div>
+                    </div>
+                    <div class="mid">
+                        <div class="notice">
+                            <div>
+                                <img src="/images/icon/alarm.png" alt="공지사항 아이콘">
+                                <span>공지사항</span>
+                                <a href="/notice/list">+</a>
+                            </div>
+                            <div class="content">
+                                <div th:if="${notice != null && notice.size() > 0}" th:onclick="moveNoticeView([[${item.getBoardNo()}]], false)" th:each="item, i:${notice}">
+                                    <div th:title="${item.getBSubject()}" th:text="${item.getBSubject()}"></div>
+                                    <span th:text="${item.getRegDate()}"></span>
+                                </div>
+                                <div th:if="${notice == null} or ${notice.size() == 0}">데이터가 없습니다.</div>
+                            </div>
+                        </div>
+                        <div class="incd">
+                            <div>
+                                <img src="/images/icon/incd.png" alt="돌발정보 아이콘">
+                                <span>돌발정보</span>
+                                <a href="/trafficMap/incidents">+</a>
+                            </div>
+                            <div class="incd-content">
+                                <div th:if="${incident != null and incident.size() > 0}" th:each="item, i:${incident}">
+                                    <div th:onclick="movePath([['/trafficMap/incidents/'+ ${item.getIncdOcrrId()}]])" th:title="${item.getIncdTitl()}" th:text="${item.getIncdTitl()}"></div>
+                                    <span th:text="${item.getAgoMinutes()}"></span>
+                                </div>
+                                <div th:if="${incident == null} or ${incident.size() == 0}">데이터가 없습니다.</div>
+                            </div>
+                        </div>
+<!--                        <div class="center" onclick="movePath('/center/center')">-->
+<!--                            <div>교통정보센터 소개 ></div>-->
+<!--                        </div>-->
+                    </div>
+                    <div class="bottom">
+                        <div class="center" onclick="movePath('/center/center')">
+                            <div>교통정보센터 소개 ></div>
+                        </div>
+                        <div>
+                            <div><img src="/images/icon/video.png" alt="동영상 아이콘">홍보 동영상</div>
+                            <div class="video-box">
+                                <video style="object-fit: fill; width: 100%; height: 100%;" controls preload="metadata" th:src="@{/video/gumi.mp4}" type="video/mp4"></video>
+                            </div>
+                        </div>
+                    </div>
+<!--                    <div class="bottom">-->
+<!--                        <div class="first-box">-->
+<!--                            <h1>POHANG</h1>-->
+<!--                            <div>포항시교통정보센터 홍보 영상입니다.</div>-->
+<!--                        </div>-->
+<!--                        <div>-->
+<!--                            <div>-->
+<!--                                <video id="video" muted playsinline controls>-->
+<!--                                    <source src="/video/video.mp4" preload="metadata" type="video/mp4">-->
+<!--                                    이 문장은 여러분의 브라우저가 video 태그를 지원하지 않을 때 화면에 표시됩니다!-->
+<!--                                </video>-->
+<!--                            </div>-->
+<!--                        </div>-->
+<!--                    </div>-->
+
+                </div>
+            </div>
+        </div>
+        <th:block th:include="include/footer.html"></th:block>
+    </body>
+</html>
+
+<script th:inline="javascript">
+    //    let isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
+
+    const popup = [[${popup}]];
+    if (popup && popup.length > 0) {
+        let top = $('header').innerHeight();
+        let left = 0;
+        let gap  = 10;
+        popup.forEach((obj, idx)=>{
+            const cookieName = 'p_' + obj.popupid;
+            const cookieInfo = getCookie(cookieName);
+
+            if (!cookieInfo) {
+                setCookie(cookieName, 'Y', null);
+            }
+            if (cookieInfo === 'N') {
+                return;
+            }
+            const popupDiv = $(`<div id="popup_${obj.popupid}" class="popup_content">
+                                    <div id="${obj.popupid}_div">
+                                        ${obj.pcontent}
+                                    </div>
+                                    <div class="close-box">
+                                        <label for="popup_check_${obj.popupid}">오늘 하루 열지 않기</label>
+                                        <input id="popup_check_${obj.popupid}" type="checkbox" value="${obj.popupid}">
+                                        <div onclick="closePopup(${obj.popupid})"> [닫기]</div>
+                                    </div>
+                                </div>`);
+            $('body').append(popupDiv);
+
+            popupDiv.draggable({containment: 'body', handle : '#'+obj.popupid +'_div'});
+
+            let incGap = gap * idx;
+            let incTop = incGap + top;
+            let incLeft = incGap + left;
+            popupDiv.css({
+                top : incTop + 'px',
+                left : incLeft + 'px'
+            })
+        })
+        const popupAlinkArr = $('.popup_content  a');
+        if (popupAlinkArr.length > 0) {
+            popupAlinkArr.attr({target : '_blank', rel : 'noreferrer noopener'});
+        }
+    }
+
+</script>

+ 5 - 1
src/main/resources/templates/statistics/congest-stat.html

@@ -64,4 +64,8 @@
 </div>
 <th:block th:include="include/footer.html"></th:block>
 </body>
-</html>
+</html>
+
+<script th:inline="javascript">
+    let _statStr = '정체 통계';
+</script>

+ 18 - 4
src/main/resources/templates/statistics/tfvl-stat-amount.html

@@ -16,10 +16,20 @@
         <div class="search-bar">
             <div>
                 <div>
-                    <div class="label">도로명</div>
-                    <select class="road_nmbr" name="road_nmbr">
+                    <div class="label">유형</div>
+                    <select class="road_type" name="road_type">
+                        <option value="road">도로</option>
+                        <option value="ixr">교차로</option>
+                    </select>
+                    <div class="label road">도로명</div>
+                    <select class="road_nmbr road" name="road_nmbr">
+                        <option th:value="ALL" th:text="전체"></option>
+                        <option th:each="item : ${road}" th:title="${item.getDaeroNm()}" th:value="${item.getNmbr()}" th:text="${item.getDaeroNm()}"></option>
+                    </select>
+                    <div class="label ixr" style="display: none">교차로명</div>
+                    <select class="ixr_id ixr" name="ixr_id" style="display: none">
                         <option th:value="ALL" th:text="전체"></option>
-                        <option th:each="item : ${road}" th:value="${item.getNmbr()}" th:text="${item.getDaeroNm()}"></option>
+                        <option th:each="item : ${ixr}" th:title="${item.getIxrNm()}" th:value="${item.getIxrId()}" th:text="${item.getIxrNm()}"></option>
                     </select>
                     <div class="label">구분</div>
                     <select class="type" name="type">
@@ -64,4 +74,8 @@
 </div>
 <th:block th:include="include/footer.html"></th:block>
 </body>
-</html>
+</html>
+
+<script th:inline="javascript">
+    let _statStr = '교통량 통계';
+</script>

+ 4 - 1
src/main/resources/templates/statistics/tfvl-stat-speed.html

@@ -67,4 +67,7 @@
 </div>
 <th:block th:include="include/footer.html"></th:block>
 </body>
-</html>
+</html>
+<script th:inline="javascript">
+    let _statStr = '소통 통계';
+</script>

+ 3 - 3
src/main/resources/templates/traffic/cctv.html

@@ -15,9 +15,9 @@
             <div id="map">
                 <th:block th:include="include/traffic.html"></th:block>
             </div>
-            <select class="mobile-select" name="mobile-select" title="cctv-목록">
-                <option value="-">CCTV 목록을 선택해주세요</option>
-            </select>
+<!--            <select class="mobile-select" name="mobile-select" title="cctv-목록">-->
+<!--                <option value="-">CCTV 목록을 선택해주세요</option>-->
+<!--            </select>-->
         </div>
         <th:block th:include="include/footer.html"></th:block>
     </body>

+ 3 - 3
src/main/resources/templates/traffic/incident.html

@@ -20,9 +20,9 @@
             <div id="map">
                 <th:block th:include="include/traffic.html"></th:block>
             </div>
-            <select class="mobile-select" name="mobile-select" title="돌발목록">
-                <option value="-">돌발 목록을 선택해주세요</option>
-            </select>
+<!--            <select class="mobile-select" name="mobile-select" title="돌발목록">-->
+<!--                <option value="-">돌발 목록을 선택해주세요</option>-->
+<!--            </select>-->
         </div>
         <th:block th:include="include/footer.html"></th:block>
     </body>

+ 3 - 3
src/main/resources/templates/traffic/intersection.html

@@ -20,9 +20,9 @@
             <div id="map">
                 <th:block th:include="include/traffic.html"></th:block>
             </div>
-            <select class="mobile-select" name="mobile-select" title="스마트 교차로 목록">
-                <option value="-">스마트 교차로 목록을 선택해주세요</option>
-            </select>
+<!--            <select class="mobile-select" name="mobile-select" title="스마트 교차로 목록">-->
+<!--                <option value="-">스마트 교차로 목록을 선택해주세요</option>-->
+<!--            </select>-->
         </div>
         <th:block th:include="include/footer.html"></th:block>
     </body>

+ 3 - 3
src/main/resources/templates/traffic/parking.html

@@ -17,9 +17,9 @@
     <div id="map">
         <th:block th:include="include/traffic.html"></th:block>
     </div>
-    <select class="mobile-select" name="mobile-select" title="주차장 목록">
-        <option value="-">주차장 목록을 선택해주세요</option>
-    </select>
+<!--    <select class="mobile-select" name="mobile-select" title="주차장 목록">-->
+<!--        <option value="-">주차장 목록을 선택해주세요</option>-->
+<!--    </select>-->
 </div>
 <th:block th:include="include/footer.html"></th:block>
 <link id="appKey" th:data-contextPath="${@environment.getProperty('kakao-key')}"/>

+ 3 - 3
src/main/resources/templates/traffic/realtimetraffic.html

@@ -20,9 +20,9 @@
             <div id="map">
                 <th:block th:include="include/traffic.html"></th:block>
             </div>
-            <select class="mobile-select" name="mobile-select" title="도로 목록">
-                <option value="-">도로 목록을 선택해주세요</option>
-            </select>
+<!--            <select class="mobile-select" name="mobile-select" title="도로 목록">-->
+<!--                <option value="-">도로 목록을 선택해주세요</option>-->
+<!--            </select>-->
         </div>
     <th:block th:include="include/footer.html"></th:block>
     </body>

+ 3 - 3
src/main/resources/templates/traffic/vms.html

@@ -20,9 +20,9 @@
             <div id="map">
                 <th:block th:include="include/traffic.html"></th:block>
             </div>
-            <select class="mobile-select" name="mobile-select" title="VMS 목록">
-                <option value="-">VMS 목록을 선택해주세요</option>
-            </select>
+<!--            <select class="mobile-select" name="mobile-select" title="VMS 목록">-->
+<!--                <option value="-">VMS 목록을 선택해주세요</option>-->
+<!--            </select>-->
         </div>
         <th:block th:include="include/footer.html"></th:block>
     </body>