junggilpark 1 рік тому
батько
коміт
1481e7e854

+ 31 - 7
src/main/java/com/its/web/controller/common/CommonController.java

@@ -10,7 +10,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
-import java.util.List;
+import java.text.SimpleDateFormat;
+import java.util.*;
 
 @Slf4j
 @RequiredArgsConstructor
@@ -45,11 +46,34 @@ public class CommonController {
         return this.service.upload(imageName);
     }
 
-//    @ApiOperation(value = "홍보동영상 소스 가져오기")
-//    @GetMapping(value="/video/{imageName}")
-//    @ResponseBody
-//    public byte[] getVideo(@PathVariable("imageName") String imageName) {
-//        return this.service.upload(imageName, "video");
-//    }
+    @ApiOperation(value = "AIP 데이터 테스트")
+    @PostMapping(value="/test/aip")
+    @ResponseBody
+    public Map<String, Map<String, List>> getVideo(@RequestParam Integer date) {
+        Map<String, Map<String, List>> resultMap = new HashMap<>();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Calendar c = Calendar.getInstance();
+        String[] strArr = {"label", "safety", "user", "device"};
+        for (String str : strArr) {
+            Map<String, List> obj = new HashMap<>();
+            obj.put("list", new ArrayList<>());
+            obj.put("date", new ArrayList<>());
+            resultMap.put(str, obj);
+        }
+
+        c.add(c.DATE, -date);
+
+        for (int ii=0; ii < date; ii++) {
+            c.add(c.DATE, 1);
+            String dateVal = sdf.format(c.getTime());
+            for ( String key : resultMap.keySet() ){
+                int value = (int)(Math.random()*3000) + 1;
+                resultMap.get(key).get("date").add(dateVal);
+                resultMap.get(key).get("list").add(value);
+            }
+        }
+
+        return resultMap;
+    }
 
 }

+ 4 - 0
src/main/java/com/its/web/dto/traffic/IntersectionCameraDto.java

@@ -104,4 +104,8 @@ public class IntersectionCameraDto {
     @ApiModelProperty("화살표 끝 Y 좌표")
     @JsonProperty("end_y")
     private Double endY;
+
+    @ApiModelProperty("설치 주소")
+    @JsonProperty("istl_lctn")
+    private String istlLctn;
 }

+ 2 - 0
src/main/java/com/its/web/mapper/its/common/CommonMapper.java

@@ -21,4 +21,6 @@ public interface CommonMapper {
     List<ConnStatisticsDto> getConnMonthStatistics(Map<String, String> paramMap);
 
     List<ConnStatisticsDto> getConnWeekStatistics(Map<String, String> paramMap);
+
+    List<ConnStatisticsDto> getConnYearStatistics(Map<String, String> paramMap);
 }

+ 2 - 2
src/main/java/com/its/web/security/WebSecurityConfig.java

@@ -37,8 +37,8 @@ public class WebSecurityConfig implements WebMvcConfigurer {
         http.headers().defaultsDisabled().contentTypeOptions();
         http.headers().xssProtection().block(false);
         http.headers().contentSecurityPolicy("default-src 'self' 'unsafe-inline' https://wowza.pohang.go.kr rtsp://220.122.218.208:1935" +
-                " http://dapi.kakao.com http://*.daumcdn.net https://*.daumcdn.net http://172.16.150.15; " +
-                "script-src 'self' blob: 'unsafe-inline' 'unsafe-eval' http://dapi.kakao.com http://*.daumcdn.net https://*.daumcdn.net; " +
+                " http://dapi.kakao.com http://*.daumcdn.net https://*.daumcdn.net http://172.16.150.15 https://t1.kakaocdn.net/kakao_js_sdk/2.7.2/kakao.min.js; " +
+                "script-src 'self' blob: 'unsafe-inline' 'unsafe-eval' http://dapi.kakao.com http://*.daumcdn.net https://*.daumcdn.net https://t1.kakaocdn.net/kakao_js_sdk/2.7.2/kakao.min.js; " +
                 "style-src 'self' 'unsafe-inline' http://dapi.kakao.com http://*.daumcdn.net https://*.daumcdn.net; " +
                 "img-src 'self' blob: data: https: 'unsafe-inline' http://dapi.kakao.com http://*.daumcdn.net https://*.daumcdn.net;" +
                 "font-src 'self' data:; media-src 'self' blob: 'unsafe-inline' https://wowza.pohang.go.kr;");

+ 3 - 0
src/main/java/com/its/web/service/common/CommonService.java

@@ -62,6 +62,9 @@ public class CommonService {
         else if ("month".equals(type)) {
             return this.mapper.getConnMonthStatistics(paramMap);
         }
+        else if ("year".equals(type)) {
+            return this.mapper.getConnYearStatistics(paramMap);
+        }
         return this.mapper.getConnStatistics(paramMap);
     }
 

+ 1 - 0
src/main/resources/application-dev.yml

@@ -30,6 +30,7 @@ spring:
       connectTimeout: 10000
 
 kakao-url: https://dapi.kakao.com/v2/maps/sdk.js?appkey=89c10f45ef100270bc75a54eb9e5b0ca&libraries=drawing&libraries=clusterer
+kakao-key: 89c10f45ef100270bc75a54eb9e5b0ca
 popup-location: C:\00.PROJECT\24.01.PHITS-WEB\uploads\popup
 board-location: C:\00.PROJECT\24.01.PHITS-WEB\uploads\board
 image-location: C:\00.PROJECT\24.01.PHITS-WEB\uploads\images

+ 12 - 12
src/main/resources/application-prod.yml

@@ -7,9 +7,9 @@ spring:
   datasource:
     hikari:
       driver-class-name: com.tmax.tibero.jdbc.TbDriver
-      jdbc-url: jdbc:tibero:thin:@115.91.94.42:8629:tibero
-      username: phutis
-      password: phutis
+      jdbc-url: jdbc:tibero:thin:172.16.10.21:8620:tibero
+      username: itsdev
+      password: itsdev
       connection-test-query: SELECT 1 FROM DUAL
       minimumIdle: 5
       maximumIdle: 10
@@ -19,18 +19,18 @@ spring:
   itcs-datasource:
     hikari:
       driver-class-name: com.tmax.tibero.jdbc.TbDriver
-      jdbc-url: jdbc:tibero:thin:@115.91.94.42:8629:tibero
-      username: itcs
-      password: itcs
+      jdbc-url: jdbc:tibero:thin:@172.16.10.21:8620:tibero
+      username: itsdev
+      password: itsdev
       connection-test-query: SELECT 1 FROM DUAL
       minimumIdle: 5
       maximumIdle: 10
       maximumPoolSize: 20
       idleTimeout: 30000
       connectTimeout: 10000
-
-kakao-url: //dapi.kakao.com/v2/maps/sdk.js?appkey=89c10f45ef100270bc75a54eb9e5b0ca&libraries=drawing&libraries=clusterer
-popup-location: C:\00.PROJECT\24.01.PHITS-WEB\uploads\popup
-board-location: C:\00.PROJECT\24.01.PHITS-WEB\uploads\board
-image-location: C:\00.PROJECT\24.01.PHITS-WEB\uploads\images
-video-location: C:\00.PROJECT\24.01.PHITS-WEB\uploads\video
+kakao-url: //dapi.kakao.com/v2/maps/sdk.js?appkey=818515fbf1c2ac66fdac8c66163c7a3e&libraries=drawing&libraries=clusterer
+kakao-key: appkey=818515fbf1c2ac66fdac8c66163c7a3e
+popup-location: C:\PHITS-WEB\uploads\popup
+board-location: C:\PHITS-WEB\uploads\board
+image-location: C:\PHITS-WEB\uploads\images
+video-location: C:\PHITS-WEB\uploads\video

+ 2 - 1
src/main/resources/mybatis/mapper/itcs/InterSectionMapper.xml

@@ -43,7 +43,8 @@
             ALS.acrd_los,
             ALS.dely_hh,
             CM.hmpg_dspl_en AS cmra_use_yn,
-            CM.hmpg_cmra_url
+            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

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

@@ -65,4 +65,17 @@
         ORDER BY TO_CHAR(TO_DATE(A.MONTHS, 'YYYYMM'), 'YYYY-MM')
     </select>
 
+    <select id="getConnYearStatistics" resultType="com.its.web.dto.common.ConnStatisticsDto" parameterType="java.util.HashMap">
+        SELECT
+            A.YEARS AS conn_dt,
+            SUM(NVL(B.conn_cnt, 0)) AS conn_cnt
+            FROM ( SELECT (TO_NUMBER(#{fromDate}) - 1 + LEVEL) YEARS FROM DUAL
+            CONNECT BY LEVEL <![CDATA[<=]]> (TO_NUMBER(#{toDate}) + 1 - TO_NUMBER(#{fromDate}))
+            ) A
+        LEFT OUTER JOIN TB_WWW_CONN_HS B ON A.YEARS = SUBSTR(B.conn_day, 0, 4)
+        GROUP BY A.YEARS
+        ORDER BY A.YEARS
+    </select>
+
+
 </mapper>

+ 44 - 2
src/main/resources/static/css/conn-statistics.css

@@ -166,8 +166,33 @@
 .list {
     width: 100%;
     height: 262px;
+    overflow: auto;
+}
+.list table{
+    border-collapse: separate;
+    border-spacing: 0;
+    width: 100%;
+}
+.list table tr {
+    height: 40px;
+}
+.list table thead {
+}
+.list table thead tr th {
+    color: rgb(51, 102, 171);
+    position: sticky;
+    top: 0;
+    background-color: white;
+    border-bottom: 2px solid rgba(102, 102, 102, 0.3);
+    width: 50%;
+}
+.list table tbody tr td {
+    text-align: center;
+    line-height: 40px;
+    color: #a1a1a1;
+    font-weight: bold;
+    width: 50%;
 }
-
 .list > div:nth-child(1) {
     width: 100%;
     height: 40px;
@@ -177,11 +202,14 @@
     justify-content: space-around;
     align-items: center;
     font-weight: bold;
+    position: sticky;
+    background-color: white;
+    top: 0;
 }
 .list > div:nth-child(2) {
     width: 100%;
     height: calc(100% - 40px);
-    overflow: auto;
+    /*overflow: auto;*/
 }
 
 .list > div:nth-child(2) > div {
@@ -198,6 +226,20 @@
     color: #a1a1a1;
     font-weight: bold;
 }
+.excel-button {
+    background-color: #1d9f1f;
+    width: 100px;
+    height: 40px;
+    border-radius: 5px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    color: white;
+}
+.excel-button:hover{
+    filter: brightness(1.1);
+}
 /*
 
 */

+ 4 - 1
src/main/resources/static/css/main.css

@@ -9,8 +9,9 @@
     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-image: url("/images/background/bg_main3.jpg");
     background-size: cover;
+    /*background-image: url("/images/background/bg_main3.jpg");*/
+    background-image: url("/images/background/background_3.jpg");
     /*z-index: 1;*/
 }
 /*.mainWrap::after {*/
@@ -148,6 +149,7 @@ body {
     -webkit-box-pack: center;
     justify-content: center;
     min-height: 529px;
+    /*opacity: 0.9;*/
 }
 
 .mainWrap .main-menu {
@@ -204,6 +206,7 @@ body {
     /*padding: 1.5rem;*/
     /*box-shadow: 2px 2px 2px 2px #eeeeee;*/
 }
+
 .mainWrap .bottom > div:first-child {
     margin-right: 5px;
 }

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


+ 84 - 52
src/main/resources/static/js/traffic/traffic.js

@@ -186,15 +186,36 @@ $(()=>{
         if (searchType === '리스트') {
             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)) {
+                        console.log(item);
+                        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 = $('.list-content.list > li').eq(ii).text().toLowerCase();
-                    if (text.includes(searchText.toLowerCase())) {
+                    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 {
@@ -374,6 +395,7 @@ function getAtrd() {
     getDataAsync("/api/traffic/atrd-vertex-all", "POST", null, null, (jsonData)=>{
         if (jsonData && jsonData.length > 0) {
             _AtrdMap = new Map();
+            console.log(jsonData);
             jsonData.forEach((obj)=>{
                 obj.list.forEach((atrd)=>{
                     const key = atrd.atrd_id +'_' + atrd.drct_cd +'_' + obj.level;
@@ -405,6 +427,7 @@ function getAtrd() {
                 },
                 {}
             );
+
             for (let key in atrdList){
                 let list = jsonData[key];
                 if (list && list.length === 2) {
@@ -521,6 +544,9 @@ function getVertex() {
  * @param AdownHillId 하행 ID
  */
 function atrdClickEvent(AupHillId, AdownHillId) {
+    if (! _MarkerHandle.findMarkerArr('atrd').length) {
+        return alert('도로정보를 불러오는 중입니다.');
+    }
     if (_MarkerHandle.selectedAtrd.length > 0) {
         _MarkerHandle.selectedAtrd.forEach((atrdId)=>{
             _MarkerHandle.findMarkerObj('atrd', atrdId).marker.setMap(null);
@@ -538,9 +564,7 @@ function atrdClickEvent(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);
 
@@ -568,13 +592,14 @@ function getAtrdLevel(level) {
 }
 
 class MarkerObj {
-    constructor({ID, NAME, X_CRDN, Y_CRDN, URL, TYPE, IMAGE, IMAGE_TYPE, DATA}) {
+    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;
@@ -894,6 +919,7 @@ class TrafficObj {
         this.infoWindow = null;
         this.polyLine = null;
         this.polyBackLine = null;
+        this.ADDR = obj.addr;
         this.init();
     }
 
@@ -1011,15 +1037,16 @@ class TbAtrdObj extends MarkerObj{
 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,
+            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'
+            IMAGE      : '/images/icon/cctv',
+            ADDR       : obj.istl_lctn_addr,
         });
         this.iwContent = this.getIwContent();
     }
@@ -1053,21 +1080,22 @@ class TbCCtvObj extends MarkerObj{
 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'
+            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',
+            ADDR        : obj.parking_addr,
         })
         this.iwContent = this.getIwContent();
     }
 
     getIwContent() {
-        const {ID, NAME, prop} = this;
+        const {ID, NAME, prop, X_CRDN, Y_CRDN} = this;
         let iwContent =
             `<div class="parking-info-window">
                         <div class="title">
@@ -1089,7 +1117,7 @@ class TbParkingObj extends MarkerObj{
                             </div>
                             <div class="row">
                                 <div>주소</div>
-                                <div title="주소 : ${prop.parking_addr}">${prop.parking_addr}</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>`;
@@ -1103,15 +1131,16 @@ class TbParkingObj extends MarkerObj{
 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',
-              DATA   : obj,
-              IMAGE_TYPE : '1',
-              IMAGE : '/images/icon/vms'
+              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();
     }
@@ -1178,16 +1207,18 @@ class TbVmsObj extends MarkerObj{
 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',
-            DATA   : obj,
-            IMAGE_TYPE : '1',
-            IMAGE : '/images/icon/incd'
-        })
+            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();
     }
 
@@ -1241,15 +1272,16 @@ 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 : "",
-                IMAGE : '/images/icon/intersection'
+                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();

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

@@ -10,6 +10,7 @@
     <th:block th:include="include/daterangepicker.html"></th:block>
     <th:block th:include="include/highchart.html"></th:block>
     <link rel="stylesheet" th:href="@{/css/conn-statistics.css}">
+    <script th:src="@{/js/export/xlsx-0.18.12.min.js}"></script>
 </head>
 <body id="body">
 <th:block th:include="include/admin-header.html"></th:block>
@@ -21,16 +22,24 @@
                 <input id="date" type="text" placeholder="날짜를 선택해주세요.">
                 <label for="date" style="display: none"></label>
                 <div th:class="button" onclick="searchStat()">조회</div>
+                <div th:class="excel-button" th:onclick="download()">
+                    <img width="25" height="25" th:src="@{/images/icon/excel.png}" alt="엑셀 다운로드" style="margin-right: 5px;">저장
+                </div>
             </div>
             <div class="chart-box">
                 <div id="chart"></div>
             </div>
             <div class="list">
-                <div>
-                    <div>기준일자</div>
-                    <div>방문자수</div>
-                </div>
-                <div class="list-content"></div>
+                <table id="table">
+                    <thead>
+                        <tr>
+                            <th>기준일자</th>
+                            <th>방문자수</th>
+                        </tr>
+                    </thead>
+                    <tbody class="list-content">
+                    </tbody>
+                </table>
             </div>
         </div>
     </div>
@@ -80,8 +89,18 @@
                 fromDate = fromDate.substring(0,6);
                 toDate = toDate.substring(0,6);
             }
+
+            if (period > 365) {
+                type = 'year';
+                typeStr = "년도";
+                fromDate = fromDate.substring(0, 4);
+                toDate = toDate.substring(0, 4);
+            }
         }
 
+        startDate = fromDate;
+        endDate = toDate;
+
         const param = {
             fromDate : fromDate,
             toDate   : toDate,
@@ -103,7 +122,8 @@
         jsonData.forEach((obj)=>{
             categories.push(obj.conn_dt);
             data.push(obj.conn_cnt);
-            $list.append($(`<div><div>${obj.conn_dt}</div><div>${obj.conn_cnt}</div></div>`))
+            // $list.append($(`<div><div>${obj.conn_dt}</div><div>${obj.conn_cnt}</div></div>`))
+            $list.append($(`<tr><td>${obj.conn_dt}</td><td>${obj.conn_cnt}</td></tr>`))
         })
         Highcharts.chart('chart', {
             chart: {
@@ -170,4 +190,24 @@
         const dateArr = dateVal.split(" ~ ");
         getConnStatistics(new Date(dateArr[0]), new Date(dateArr[1]));
     }
+
+    /**
+     * Table to Excel Dawnload
+     */
+    function download() {
+        const table = document.getElementById('table');
+        const wb = XLSX.utils.book_new(); //새 페이지 생성
+        const newWorksheet = XLSX.utils.table_to_sheet(table); //테이블 TO SHEET
+        XLSX.utils.book_append_sheet(wb, newWorksheet, '접속자 통계'); // 새 페이지, SHEET, SHEET 명칭,
+        const wbout = XLSX.write(wb, {bookType:'xlsx', type: 'base64'}); // binary 데이터 생성
+        const base64Url = 'data:application/octet-stream;base64,' + wbout;
+        const excelFileName = '접속자 통계 (' + startDate + '~' + endDate + ').xlsx';
+        const link = document.createElement('a');
+        link.href = base64Url;
+        link.download = excelFileName;
+        $('body').append(link);
+        link.click();
+        link.remove();
+    }
+
 </script>

+ 27 - 1
src/main/resources/templates/traffic/parking.html

@@ -8,6 +8,8 @@
     <title>포항시 교통정보센터</title>
     <th:block th:include="include/head.html"></th:block>
     <th:block th:include="include/trafficHead.html"></th:block>
+    <script src="https://t1.kakaocdn.net/kakao_js_sdk/2.7.2/kakao.min.js"
+            integrity="sha384-TiCUE00h649CAMonG018J2ujOgDKW/kVWlChEuu4jK2vxfAAD0eZxzCKakxg55G4" crossorigin="anonymous"></script>
 </head>
 <body id="body">
 <th:block th:include="include/header.html"></th:block>
@@ -20,5 +22,29 @@
     </select>
 </div>
 <th:block th:include="include/footer.html"></th:block>
+<link id="appKey" th:data-contextPath="${@environment.getProperty('kakao-key')}"/>
 </body>
-</html>
+</html>
+<script th:inline="javascript">
+    if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
+        const APP_KEY = document.getElementById("appKey").getAttribute("data-contextPath");
+        Kakao.init(APP_KEY);
+    }
+
+    function openNavigationApp(xCrdn, yCrdn, addr) {
+        if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
+            appNavi(xCrdn, yCrdn, addr)
+        }
+        else {
+            window.open(`https://map.kakao.com/link/to/${addr},${yCrdn},${xCrdn}`);
+        }
+    }
+    function appNavi(xCrdn, yCrdn, addr) {
+        Kakao.Navi.share({
+            name: addr,
+            x: Number(xCrdn),
+            y: Number(yCrdn),
+            coordType: 'wgs84'
+        });
+    }
+</script>