let _size = [48, 48, 48, 48, 40, 32, 24, 22, 20, 18, 18]; let _AtrdData = []; let _AtrdMap = new Map(); let selectIncidentId = null; //시설물 유형 const _FacilityArray = ['cctv', 'vms', 'intersection', 'incident', 'traffic', 'parking', 'intersectionCamera']; 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 이미지 표출 시간 let _MapHandler; //카카오 맵 핸들러 let _Level = 6; //현재 줌레벨 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(); // 시설물 유형 정보로 flag 값 세팅 if (_type) { _MapHandler[_type + 'Flag'] = true; _MapHandler[_type + 'ListFlag'] = 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 = type + 'Camera'; } if (type === 'traffic') { subType = 'atrd'; getVertex(); } else { if (_MapHandler[type] && _MapHandler[type].length <= 0) { _methodMap.get(type)(); } } let toggle = 'hide'; let classToggle = 'removeClass'; if (isShow === true) { toggle = 'show'; classToggle = 'addClass'; } legendIcon[classToggle]('active'); target[classToggle]('active'); _MapHandler[toggle](type); if (subType) { _MapHandler[toggle](subType); } }) /** * 범례 호버 색 변환 * */ $('.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'); } } }); let _MarkerArr = []; $('.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 searchType = $('.tab-title > div.active').text(); const searchText = $(this).val(); if (searchType === '리스트') { const $list = $('.list-content.list > li'); if (!$list.length) return; 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())) { li.css('display', 'block'); } else { li.css('display', 'none'); } } } } else { if (_MarkerArr.length > 0) { _MarkerArr.forEach((obj)=>{ obj.setMap(null); }); _MarkerArr = []; } $('.list-content.spot').html(""); if (!searchText || !searchText.trim()) return; $.ajax({ url: 'https://dapi.kakao.com/v2/local/search/address.json', headers: { 'Authorization': 'KakaoAK 4896b94398b96949d349881d004835cf'}, data :{ query : searchText, }, type: 'GET' }).done(function(data) { if (data && data.documents.length > 0) { let str = ""; const setMap = new Map(); const bounds = new kakao.maps.LatLngBounds(); let minX; let minY; let maxX; let maxY; let idx = 0; data.documents.forEach((obj)=>{ if (!setMap.get(obj.address_name) && obj.address_name.includes("경북 포항시")) { 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+= `
  • ${obj.address_name}
  • `; } } }); if (minX && minY && maxX && maxY) { bounds.extend(new kakao.maps.LatLng(minY, minX)); bounds.extend(new kakao.maps.LatLng(maxY, maxX)); _MapHandler.map.setBounds(bounds); $('.list-content.spot').html(str); getVertex(); } } }); } }) }); /** * 간선도로 마커 레벨별 이미지 보이기 감추기 */ function atrdMarkerResize() { let level = _Level; let atrdArr = _MapHandler['atrd']; atrdArr.forEach((atrd)=>{ setMarkerImage(atrd, atrd.obj.drct_cd, true); }); if (level === 3) { level = 4; } if (level >= 8) { level = 7; } else if (level >= 6) { level = 6; } const atrdInfo = _AtrdMap.get(level.toString()); if (atrdInfo) { _AtrdData = []; let upHillArr = []; let downHillArr = []; const $selectedLi = $(".left-list-area .list-content.list > li.click"); let id; let idArr; if ($selectedLi[0]) { id = $selectedLi.attr('id'); } if (id) { idArr = id.split("_"); } atrdInfo.forEach((atrd)=>{ if (atrd.atrd_id.toString() === idArr[1] || atrd.atrd_id.toString() === idArr[2]) { _AtrdData.push(atrd); if (atrd.atrd_id.toString() === idArr[1]) { upHillArr.push(atrd); } else { downHillArr.push(atrd); } } }); if (upHillArr[0]) { let upHillX = upHillArr[0].x_crdn_arr.split(',')[0]; let upHillY = upHillArr[0].y_crdn_arr.split(',')[0]; atrdArr[0].marker.setPosition(getKakaoPosition(upHillY, upHillX)); } if (downHillArr[0]) { let downHillX = downHillArr[0].x_crdn_arr.split(',')[0]; let downHillY = downHillArr[0].y_crdn_arr.split(',')[0]; atrdArr[1].marker.setPosition(getKakaoPosition(downHillY, downHillX)); } } } /** * 스마트교차로 카메라 레벨별 이미지 보이기 감추기 */ function intersectionCameraChangeWidthZoomLevel() { const level = _Level; const size = [48, 48, 48] const levelSize = size[level]; const type = 'intersectionCamera'; const markerArr = _MapHandler[type]; const map = _MapHandler.map; if (markerArr.length > 0) { markerArr.forEach((obj) => { const content = $('
    '); content.css( { width: levelSize+ 'px', height: levelSize+ 'px', backgroundImage:'url(/images/icon/intersection-cctv.png)', backgroundSize : levelSize+'px '+levelSize+'px', backgroundRepeat: 'no-repeat', backgroundPosition : 'center', transform : 'rotate(' + obj.obj.cmra_angn +'deg)', cursor : 'pointer' }); obj.marker.setContent(content[0]); content.on('click', ()=> { _MapHandler.click(type, obj.ID); }); if (level <= 2 && _MapHandler['intersectionFlag']) { obj.marker.setMap(map); obj.polyline.setMap(map); } else { obj.marker.setMap(null); obj.polyline.setMap(null); } }) } } /** * 스마트교차로 레벨별 이미지 보이기 감추기 */ function intersectionMarkerChangeWithZoomLevel() { const level = _Level; if (_MapHandler['intersection'].length > 0) { _MapHandler['intersection'].forEach((obj)=>{ setMarkerImage(obj, "", false); if (level > 2 && _MapHandler['intersectionFlag']) { obj.marker.setVisible(true); } else { obj.marker.setVisible(false); } }) } } /** * 맵 레벨별 이미지 변환 * @param array */ function markerSizeChangeWithZoomLevel(array) { if (array && array.length > 0) { array.forEach((obj)=>{ if (obj.isClick) { setMarkerImage(obj, 2, false) } else { setMarkerImage(obj, 1, false); } }); } } /** * 지점 클릭 위치 이동 * @param xCoordinate x좌표 * @param yCoordinate y좌표 * @param listIndex 지점 선택 인덱스 */ function moveLocation(xCoordinate, yCoordinate, listIndex) { const position = getKakaoPosition(yCoordinate, xCoordinate); _MapHandler.map.setCenter(position); _MapHandler.map.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, (jsonData)=>{ if (jsonData && jsonData.length > 0) { receiveFacilityData(jsonData, TbCCtvObj,'cctv'); } }, 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'); } }, null); } /** * 돌발정보 */ function getIncident () { getDataAsync('/api/traffic/incident-list', 'POST', null, null, (jsonData)=>{ if (jsonData && jsonData.length > 0) { receiveFacilityData(jsonData, TbIncdObj, 'incident'); if (selectIncidentId) { _MapHandler.click('incident', selectIncidentId); } } }, 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); } }) receiveFacilityData(data, IntersectionObj, 'intersection'); } }, null); } /** * 주차정보 */ function getParking() { getDataAsync('/api/traffic/parking-list', 'POST', null, null, (jsonData)=>{ if (jsonData && jsonData.length > 0) { receiveFacilityData(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)=>{ _AtrdMap.set(obj.level, obj.list); }); } }, null); getDataAsync('/api/traffic/atrd-list', 'POST', null, null, (jsonData)=>{ if (jsonData) { let listStr = ""; let mobileStr = ""; let atrdData = []; for (let key in jsonData){ atrdData.push({ name : key, list : jsonData[key] }); } atrdData.sort((a,b)=>{ return a.name > b.name ? 1 : a.name < b.name ? -1 : 0; }); atrdData.forEach((obj)=>{ let upHillId = null; let downHillId = null; const list = obj.list; if (list && list.length === 2) { upHillId = list[0].atrd_id; downHillId = list[1].atrd_id; if (list[0].drct_cd === "1") { upHillId = list[1].atrd_id; downHillId = list[0].atrd_id; } } listStr += `
  • ${obj.name}
  • ` mobileStr += `` }); 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 { const array = _MapHandler['atrd']; if (array.length > 0) { array.forEach((atrd)=>{ atrd.close(); }) _MapHandler['atrd'] = []; getVertex(); } } }) const listSection = $('.left-list-area .list-content.list'); listSection.empty(); listSection.html(listStr); } }); getVertex(); } /** * 소통정보 가져오기 */ function getVertex() { const array = _MapHandler['traffic']; if (array.length > 0) { array.forEach((obj) => { obj.setVisibleMarker(false); if (obj.infoWindow) { obj.infoWindow.setMap(null); } }) _MapHandler['traffic'] = []; } let level = _MapHandler.map.getLevel(); const bounds = _MapHandler.map.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(), } if (_MapHandler.atrd && _MapHandler.atrd.length > 0) { _MapHandler.atrd.forEach((atrd)=>{ atrd.cnt = 0; atrd.trvl_hh = 0; atrd.sped = 0; }) } getDataAsync('/api/traffic/vertex-list', 'POST', data, null, (jsonData)=>{ if (jsonData && jsonData.length > 0) { jsonData.forEach((obj)=>{ const trafficObj = new TrafficObj(obj); if (_AtrdData.length > 0) { _AtrdData.forEach((atrd)=>{ if (atrd.road_id.toString() === trafficObj.ID.toString()) { let sped = Number(obj.sped); let trvl_hh = Number(obj.trvl_hh); const atrdObj = _MapHandler.getSelectObj(_MapHandler['atrd'], atrd.atrd_id); if (!isNaN(sped)) { atrdObj.sped += sped; } if (!isNaN(trvl_hh)) { atrdObj.trvl_hh += trvl_hh; } atrdObj.cnt += 1; trafficObj.polyBackLine.setOptions({ strokeColor : 'black', }); } }) } _MapHandler['traffic'].push(trafficObj); }) } }); } /** * 간선도로 클릭 이벤트 * @param AupHillId 상행 ID * @param AdownHillId 하행 ID */ function atrdClickEvent(AupHillId, AdownHillId) { let atrdArr = _MapHandler['atrd']; if (atrdArr.length > 0) { atrdArr.forEach((atrd)=>{ atrd.close(); }); _MapHandler['atrd'] = []; } const selectedObj = _MapHandler.selectedObj; if (selectedObj) { _MapHandler.close(selectedObj.type, selectedObj.ID); } _AtrdData = []; const $selectedLi = $('.left-list-area .list-content.list > li.click'); if ($selectedLi) { $selectedLi.removeClass('click'); } const $selectLi = $('#atrd_' + AupHillId + "_" + AdownHillId); $selectLi.addClass('click'); $selectLi.focus(); //상행, 하행 ID 가 있다면 실행 if (AupHillId && AdownHillId) { const upHillArr = []; const downHillArr = []; const bounds = new kakao.maps.LatLngBounds(); //전체 간선도로 목록을 담은 Map 에서 기준값을 5레벨로 잡아 포커스 좌표를 설정 if (_AtrdMap.get("5")) { const basicAtrd = _AtrdMap.get("5"); for (let idx in basicAtrd) { const obj = basicAtrd[idx]; if (obj.atrd_id.toString() === AupHillId || obj.atrd_id.toString() === AdownHillId) { bounds.extend(new kakao.maps.LatLng(obj.y_crdn_min, obj.x_crdn_min)); bounds.extend(new kakao.maps.LatLng(obj.y_crdn_max, obj.x_crdn_max)); } } _MapHandler.map.setBounds(bounds); //전체 레벨이 다있는게 아니므로 로드 레벨은 재설정 let level = _Level; if (level >= 8) { level = 7; } else if (level >= 6) { level = 6; } /** * 범위를 포커스 하게 되면 레벨이 변경되므로 변경 된 레벨의 간선도로 목록에서 * 상행, 하행 ID가 포함된 데이터 갖고오며 상행, 하행 데이터 따로 구분 */ const atrdInfo = _AtrdMap.get(level.toString()); if (atrdInfo) { atrdInfo.forEach((atrd)=>{ if (atrd.atrd_id.toString() === AupHillId.toString() || atrd.atrd_id.toString() === AdownHillId.toString()) { _AtrdData.push(atrd); if (atrd.atrd_id === AupHillId) { upHillArr.push(atrd); } else { downHillArr.push(atrd); } } }) } //레벨이 변경됐으므로 다시한번 버텍스를 초기화해준다 getVertex(); //각 상행 하행의 첫번째 좌표를 가지고 상행, 하행 아이콘을 생성 하며 객체는 array에 보관해둠 if (upHillArr[0]) { const upHillObj = new TbAtrdObj(upHillArr[0]); _MapHandler['atrd'].push(upHillObj); } if (downHillArr[0]) { const downHillObj = new TbAtrdObj(downHillArr[0]); _MapHandler['atrd'].push(downHillObj); } } } } /** * 간선도로 상,하행 마커 그리기 * @param src 상, 하행 코드 * @param lat X 좌표 * @param lng Y 좌표 * @param name 명칭 * @returns {daum.maps.Marker} 생성 마커 */ function drawAtrdMakrer(src, lng, lat, name) { let imageSize; let imageOption; let imageSrc = '/images/icon/atrd' + src + '.png'; //레벨별 사이즈를 다르게 표출 const level = _Level; let size = 48; if (level >= 7) { size = 24; } imageSize = new kakao.maps.Size(size, size); imageOption = { offset: new kakao.maps.Point(size/2, size), }; let markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imageOption); let markerPosition = new kakao.maps.LatLng(lng, lat); let atrdSideMarker = new kakao.maps.Marker({ position: markerPosition, image: markerImage, zIndex: 5, title: name, }); return atrdSideMarker; } /** * 소통정보 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.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; } 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(_MapHandler['trafficFlag']); new kakao.maps.event.addListener(this.polyLine, 'mouseover', function (event) { this.setOptions({strokeColor: '#0000FF'}); const iwContent = `
    ${_self.NAME} ${trafficObj.grad_nm}
    ${trafficObj.strt_nm_node} → ${trafficObj.end_nm_node}
    소요시간 : 약 ${textFormat(trafficObj.trvl_hh)}분   속도 : 약 ${textFormat(trafficObj.sped)}km/h
    `; _self.infoWindow = new kakao.maps.CustomOverlay({ map: _MapHandler.map, clickable: true, position: event.latLng, content: iwContent, xAnchor: -0.1, yAnchor: 1.1, zIndex: 4 }); }); 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 { constructor(obj) { this.ID = obj.atrd_id; this.NAME = obj.atrd_nm; this.X_CRDN = null; this.Y_CRDN = null; this.obj = obj; this.marker = null; this.imgSrc = '/images/icon/atrd'; this.isClick = false; this.sped = 0; this.trvl_hh = 0; this.cnt = 0; this.infoWindow = null; this.init(); } init() { if (this.obj.x_crdn_arr) { this.X_CRDN = this.obj.x_crdn_arr.split(",")[0]; } if (this.obj.y_crdn_arr) { this.Y_CRDN = this.obj.y_crdn_arr.split(",")[0]; } const name = this.NAME + " [" + this.obj.drct_nm + "]"; this.marker = drawAtrdMakrer(this.obj.drct_cd, this.Y_CRDN, this.X_CRDN, name); this.click(); // let _self = this; // new kakao.maps.event.addListener(this.marker, 'mouseover', function (event) { // let position = getKakaoPosition(_self.Y_CRDN, _self.X_CRDN); // const iwContent = // `
    //
    // ${_self.NAME} // [${_self.obj.drct_nm}] //
    //
    // 평균속도 : ${Math.round(_self.sped/_self.cnt)} km/h
    // 통행시간 : ${textFormat(Math.round(_self.trvl_hh/_self.trvl_hh))}분 //
    //
    `; // _self.infoWindow = new kakao.maps.CustomOverlay({ // map: _MapHandler.map, // clickable: true, // position: position, // content: iwContent, // xAnchor: -0.1, // yAnchor: 1.1, // zIndex: 4 // }); // }); // new kakao.maps.event.addListener(this.marker, 'mouseout', function (event) { // _self.infoWindow.setMap(null); // }) } click() { this.setVisibleMarker(true); } setVisibleMarker(isVisible) { let visible = isVisible ? _MapHandler.map : null; this.marker.setMap(visible); this.isClick = isVisible; } close() { this.setVisibleMarker(false); } } /** * CCTV Object */ class TbCCtvObj { constructor(obj) { this.ID = obj.cctv_mngm_nmbr; this.NAME = obj.istl_lctn_nm; this.X_CRDN = obj.x_crdn; this.Y_CRDN = obj.y_crdn; this.URL = obj.strm_http_addr; this.type = 'cctv'; this.obj = obj; this.marker = null; this.infoWindow = null; this.iwContent = null; this.isClick = false; this.imgSrc = '/images/icon/cctv'; this.timer = null; } init() { this.marker = createMarker(this, 'cctv'); this.iwContent = `
    ${this.NAME}
    X
    ※ CCTV영상은 30초간 제공됩니다.
    계속재생
    `; } setVisibleMarker(isVisible) { this.marker.setVisible(isVisible); } } /** * Parking Object */ class TbParkingObj { constructor(obj) { this.ID = obj.parking_id; this.NAME = obj.parking_nm; this.X_CRDN = obj.x_crdn; this.Y_CRDN = obj.y_crdn; this.obj = obj; this.marker = null; this.infoWindow = null; this.iwContent = null; this.isClick = false; this.imgSrc = '/images/icon/parking'; this.timer = null; this.type = 'parking'; } init() { this.marker = createMarker(this, 'parking'); this.iwContent = `
    ${this.NAME}
    주차면수
    ${this.obj.parking_num} 대
    구분
    ${this.obj.parking_type_desc}
    기본요금
    ${this.obj.parking_fee_type_desc}
    주소
    ${this.obj.parking_addr}
    `; } setVisibleMarker(isVisible) { this.marker.setVisible(isVisible); } } /** * VMS Object */ class TbVmsObj { constructor(obj) { this.ID = obj.vms_ctlr_nmbr; this.NAME = obj.vms_nm; this.X_CRDN = obj.x_crdn; this.Y_CRDN = obj.y_crdn; this.obj = obj; this.marker = null; this.infoWindow = null; this.iwContent = null; this.isClick = false; this.imgSrc = '/images/icon/vms'; this.timer = null; this.phaseArray = []; this.type = 'vms'; } init() { this.marker = createMarker(this, 'vms'); this.phaseArray = []; let width; let height; let isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); switch (this.obj.vms_type_cd) { case 'VMC1': width = 400; height = 64; break; case 'VMC2': width = 384; height = 64; break; case 'VMC3': width = 288; height = 160; break; case 'VMC4': width = 256; height = 192; break; } const windowHeight = $(window).height(); if (isMobile || windowHeight < 900) { width = width/2; height = height/2; } width = width +'px'; height = height + 'px'; const msg = this.obj.msg; if (!msg || msg.length === 0) { width = '256px'; height = '50px'; } let iwContent = `
    ${this.NAME}
    X
    `; if (msg && msg.length > 0) { for (let idx in msg) { let msgObj = msg[idx]; let className = ''; if (idx === "0") { className = 'active' } iwContent += ``; this.phaseArray.push(msgObj.phase); } } else { iwContent += '표출 이미지 데이터가 없습니다.'; } iwContent += `
    `; this.iwContent = iwContent; } setVisibleMarker(isVisible) { this.marker.setVisible(isVisible); } } /** * 돌발정보 Object */ class TbIncdObj { constructor(obj) { this.ID = obj.incd_ocrr_id; this.NAME = obj.incd_titl; this.X_CRDN = obj.x_crdn; this.Y_CRDN = obj.y_crdn; this.obj = obj; this.marker = null; this.infoWindow = null; this.isClick = false; this.imgSrc = '/images/icon/incd'; this.iwContent = null; this.type = 'incident'; } init() { this.marker = createMarker(this, 'incident'); this.iwContent = `
    ${this.NAME}
    X
    위치 : ${this.obj.road_nm}
    설명 : ${this.obj.incd_expl}
    기간 : ${this.obj.incd_strt_dt} ~ ${this.obj.incd_end_prar_dt}
    `; } setVisibleMarker(isVisible) { this.marker.setVisible(isVisible); } } /** * 스마트 교차로 Object */ class IntersectionObj { constructor(obj) { this.ID = obj.ixr_id; this.NAME = obj.ixr_nm; this.X_CRDN = obj.x_crdn; this.Y_CRDN = obj.y_crdn; this.obj = obj; this.marker = null; this.infoWindow = null; this.isClick = false; this.imgSrc = '/images/icon/intersection'; this.detail = []; this.type = 'intersection'; } init() { let imageSrc = this.imgSrc + '.png', // 마커이미지의 주소입니다 size = _size[_Level], imageSize = new kakao.maps.Size(size, size), // 마커이미지의 크기입니다 imageOption = { offset: new kakao.maps.Point(size/2, size/2), alt: this.NAME, }; // 마커의 이미지정보를 가지고 있는 마커이미지를 생성합니다 let markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imageOption), markerPosition = getKakaoPosition(this.Y_CRDN, this.X_CRDN); // 마커가 표시될 위치입니다 this.marker = new kakao.maps.Marker( {map: _MapHandler.map, position: markerPosition, image: markerImage, zindex: 10, clickable: true, title : this.NAME} ); const level = _Level; const isVisible = (level > 2 && _MapHandler['intersectionFlag']); this.marker.setVisible(isVisible); let _self = this; new kakao.maps.event.addListener(this.marker, 'click', ()=> _MapHandler.click('intersection', _self.ID)); } setVisibleMarker(isVisible) { if (_Level <= 2) isVisible = false; this.marker.setVisible(isVisible); } } /** * 스마트교차로 카메라 Object */ class IntersectionCameraObj { constructor(obj) { this.ID = obj.cmra_id + '_' + obj.drct_dvsn_cd; this.NAME = obj.drct_lctn; this.X_CRDN = obj.cmra_x_crdn; this.Y_CRDN = obj.cmra_y_crdn; this.URL = obj.hmpg_cmra_url; this.obj = obj; this.marker = null; this.infoWindow = null; this.iwContent = null; this.isClick = false; this.timer = null; this.polyline = null; this.type = 'intersectionCamera'; } init() { this.marker = createIntersectionCameraMarker(this); this.iwContent = `
    ${this.NAME}
    X
    ※ CCTV영상은 30초간 제공됩니다.
    계속재생
    `; } setVisibleMarker(isVisible) { const visible = isVisible && _Level <= 2 ? _MapHandler.map : null; this.marker.setMap(visible); this.polyline.setMap(visible); } } /** * 인포윈도우 이벤트 * @param type 시설물 유형 * @param id 요소 ID * @param event 이벤트 종류 (click, close) */ function infoWindowEvent(type, id, event) { _MapHandler[event](type, id); } let isHide = false /** * 좌측 목록 토글 이벤트 */ function toggleEvent() { const $listArea = $('.left-list-area'); const $toggleButton = $('.toggle-button'); if (!isHide) { $toggleButton.animate({ left: 0 }, 'slow'); $listArea .animate({ left: -$listArea.width() }, 'slow'); $toggleButton.text('>'); } else { $toggleButton.animate({ left: $listArea.width() }, 'slow'); $listArea .animate({ left: 0 }, 'slow'); $toggleButton.text('<'); } isHide = !isHide; } window.addEventListener('resize', function(event) { if ($(this).width() > 450) { const $toggleButton = $('.toggle-button'); const $listArea = $('.left-list-area'); const left = $toggleButton.offset().left; const listLeft = $listArea.offset().left; if ($(this).width() >= 920) { if (left > 0 && left < 400) { $toggleButton.css('left', 400); } if (listLeft > -400 && listLeft < 0) { $listArea.css('left', -400); } } else { if (left > 0 && left > 275) { $toggleButton.css('left', 275); } if (listLeft < -273) { $listArea.css('left', -273); } } } }) /** * 시설물 마커 초기화 * @param obj 시설물 객체 * @returns {kakao.maps.Marker} */ function createMarker(obj, type) { let map = _MapHandler.map; let imageSrc = obj.imgSrc + '1.png', // 마커이미지의 주소입니다 size = _size[map.getLevel()], imageSize = new kakao.maps.Size(size, size), // 마커이미지의 크기입니다 imageOption = { offset: new kakao.maps.Point(size/2, size/2), alt: obj.NAME, }; // 마커의 이미지정보를 가지고 있는 마커이미지를 생성합니다 let markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imageOption), markerPosition = getKakaoPosition(obj.Y_CRDN, obj.X_CRDN); // 마커가 표시될 위치입니다 let marker = new kakao.maps.Marker( {map: map, position: markerPosition, image: markerImage, zindex: 10, clickable: true, title : obj.NAME} ); let flag = _MapHandler[type+'Flag']; marker.setVisible(flag); kakao.maps.event.addListener(marker, 'mouseover', function () { if (!obj.isClick) { setMarkerImage(obj, 2, false); } }); kakao.maps.event.addListener(marker, 'mouseout', function () { if (!obj.isClick) { setMarkerImage(obj, 1, false) } }); kakao.maps.event.addListener(marker, 'click', ()=> _MapHandler.click(type, obj.ID)); return marker; } /** * 스마트 교차로 카메라 마커 초기화 * @param obj 스마트 교차로 카메라 객체 * @returns {kakao.maps.Marker} */ function createIntersectionCameraMarker(obj) { const position = getKakaoPosition(obj.Y_CRDN, obj.X_CRDN); const content = $('
    '); const angle = Number(obj.obj.cmra_angn); content.css( { width: '38.45px', height: '38.45px', backgroundImage:'url(/images/icon/intersection-cctv.png)', backgroundSize : '38.45px 38.45px', backgroundRepeat: 'no-repeat', backgroundPosition : 'center', transform : 'rotate(' + angle +'deg)' }); const marker = new kakao.maps.CustomOverlay({ content: content[0], position: position, zindex: 15, }); const linePath1 = [obj.obj.start_x, obj.obj.start_y] const linePath2 = [obj.obj.end_x, obj.obj.end_y]; const color = ['#888888', '#15B337', '#15B337', '#15B337', '#FFAA00', '#FFAA00', '#EB260C', '#EB260C', '#EB260C']; obj.polyline = new kakao.maps.Polyline({ path: [ getKakaoPosition(linePath1[1], linePath1[0]), getKakaoPosition(linePath2[1], linePath2[0]), ], strokeWeight: 10, strokeColor: color[obj.obj.acrd_los], strokeOpacity: 1, strokeStyle: 'solid', endArrow: true, name : obj.NAME, }); if (_Level <= 2 && _MapHandler['intersectionFlag']){ marker.setMap(_MapHandler.map); obj.polyline.setMap(_MapHandler.map); } content.on('click', ()=> { obj.click(); }); return marker; } /** * 시설물 이미지 유형 변경 이벤트 * @param obj 시설물 객체 * @param type 시설물 이미지 유형 */ function setMarkerImage(obj, type, isAtrd) { const currentLevel = _Level; let size = _size[currentLevel]; let point1 = size/2; let point2 = size/2; if (isAtrd) { size = ( currentLevel >= 7 ) ? 24 : 48; point1 = size/2; point2 = size; } const imageSize = new kakao.maps.Size(size, size); const imageSrc = obj.imgSrc + type +'.png'; const imageOption = { offset: new kakao.maps.Point(point1, point2), alt: obj.NAME }; const markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imageOption); obj.marker.setImage(markerImage); } /** * 선택 리스트 스크롤 높이 반환 이벤트 * @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 수신 데이터 * @param array 시설물 Object Array List * @param facilityClass 시설물 Object Class * @param listFlag 시설물 리스트 목록 표출 플래그 * @param type 시설물 유형 * @returns {*[]} 시설물 Object Array List */ function receiveFacilityData(jsonData, facilityClass, type) { if (_MapHandler[type].length > 0) { _MapHandler[type].forEach((obj)=>{ if (obj.marker) { obj.marker.setMap(null); } if (obj.polyline) { obj.polyline.setMap(null); } }); _MapHandler[type] = []; } let listStr = ""; let mobileStr = ""; if (jsonData && jsonData.length > 0) { jsonData.forEach((obj)=>{ const marker = new facilityClass(obj); marker.init(); if (type === 'intersection') { if (obj.detail && obj.detail.length > 0) { obj.detail.forEach((cameraObj)=>{ const camera = new IntersectionCameraObj(cameraObj); camera.init(); _MapHandler[type + 'Camera'].push(camera); }) } } listStr += `
  • ${marker.NAME}
  • ` mobileStr += ``; _MapHandler[type].push(marker); }); if (_MapHandler[type + 'Flag']) { _MapHandler.show(type); } } if (_MapHandler[type + 'ListFlag'] === true) { 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'); } }) } } /** * 맵 중앙 아이콘 위 위치 좌표 * @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; return [top, left]; } /** * 맵 드래그 적용 */ function setInfoWindowPositionWidthDraggable(markerObj, type) { const {infoWindow, ID} = markerObj; const position = getInfoWidowPosition(infoWindow); let top = position[0]; let left = position[1]; infoWindow.css({ top : top + 'px', left : left + 'px', position : 'absolute', zIndex : 999, }); infoWindow.draggable({containment : 'body', handle: '.'+ type + '-name-' + ID}); } /** * 카카오 포지션 지정 */ function getKakaoPosition(yCoordinate, xCoordinate) { return new kakao.maps.LatLng(Number(yCoordinate), Number(xCoordinate)); } /** * videoJs 객체 생성 */ function createVideoJs(id, url) { 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', ()=>{ if (video.error().code === 4) { video.pause(); video.dispose(); const $errorBox = $('.content > div:nth-child(1)'); $errorBox.append($('스트리밍 오류 이미지')); $errorBox.css({ display : 'flex', alignItems : 'center', justifyContent : 'center', }); video = null; } }); return video; } class MapHandler { constructor(id) { this.selectedObj = null; this.map = null; this.mapElement = id; this.atrd = []; } init () { //시설물별 배열, 토글 플래그, 리스트 플래그 생성 _FacilityArray.forEach((type)=>{ this[type] = []; this[type + 'Flag'] = false; this[type + 'ListFlag'] = false; }); 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); const handler = this; /** * 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 markerArr = handler[type]; const markerFlag = handler[type + 'Flag']; markerSizeChangeWithZoomLevel(markerArr, markerFlag); }); intersectionMarkerChangeWithZoomLevel(); intersectionCameraChangeWidthZoomLevel(); if (handler['atrd'].length > 0) { atrdMarkerResize(); } getVertex(); }); /** * 좌표 데이터가의 양이 많으므로 이동 할때마다 영역별 소통정보를 새로 그려줌 */ kakao.maps.event.addListener(this.map, 'dragend', function() { getVertex(); }); } //마커 클릭 이벤트 click(type, id) { const markerArr = this[type]; // 시설물 배열 const markerFlag = this[ type + 'Flag']; // 시설물 토글 플래그 const markerListFlag = this[ type + 'ListFlag']; // 시설물 토글 플래그 const atrdArr = this['atrd']; if (atrdArr && atrdArr.length > 0) { atrdArr.forEach((atrd)=>{ atrd.close(); }); this['atrd'] = []; _AtrdData = []; const $selectedLi = $('.left-list-area .list-content.list > li.click'); if ($selectedLi) { $selectedLi.removeClass('click'); } } let selectObj = this.selectedObj; // 이전 클릭 객체 let clickObj = this.getSelectObj(markerArr, id); // 현재 클릭 객체 if (selectObj) { if (selectObj === clickObj && type !== 'intersection') { return; } _MapHandler.close(selectObj.type, selectObj.ID); } const coordinates = getKakaoPosition(clickObj.Y_CRDN, clickObj.X_CRDN); this.selectedObj = clickObj; const selectedLi = $('#'+type+'-' + clickObj.ID); selectedLi.addClass('click'); selectedLi.focus(); this.map.setCenter(coordinates); if (this['trafficFlag']) { getVertex(); } if (clickObj.iwContent) { // 인포 윈도우가 있을때만 실행 clickObj.infoWindow = $(clickObj.iwContent); $('body').append(clickObj.infoWindow); setInfoWindowPositionWidthDraggable(clickObj, type); } if (clickObj.URL) { // Url 유무로 영상 이벤트 실행 this.videoEvent(clickObj); } if (type === 'vms') { // vms 일 경우 이미지 표출실행 this.vmsEvent(clickObj); } if (type === 'intersection') { // 스마트 교차로는 줌레벨 2로 변경 this.map.setLevel(2); } else if (clickObj.imgSrc) { // img src 값이 있는 객체만 클릭 이미지 변경 setMarkerImage(clickObj, 2, false); } clickObj.isClick = true; if (markerListFlag) { moveToScroll(markerFlag, markerArr, clickObj.ID); // 클릭 시 리스트 이동 } } //마커 이벤트 종료 close(type, id) { const markerArr = this[type]; const listFlag = this[type + 'ListFlag']; const closeObj = this.getSelectObj(markerArr, id); if (closeObj) { closeObj.isClick = false; this.selectedObj = null; if (!type.includes('intersection')) { // 스마트교차로 아닌 화면은 이미지를 원래대로 돌린다. setMarkerImage(closeObj, 1, false); } let oldPlayer = document.getElementById("video-" + closeObj.ID); let selectedLi = $("#" + type + "-" + closeObj.ID); if (selectedLi.hasClass('click')) { // 선택 리스트가 있는지 여부 selectedLi.removeClass('click'); } if (listFlag) { // 리스트 플래그 있는지 여부 $('.mobile-select').val("-"); } if (oldPlayer) { // 켜져있던 영상이 있는지 여부 videojs(oldPlayer).dispose(); } if (closeObj.infoWindow) { // 인포 윈도우를 화면에 올려놨는지 closeObj.infoWindow.remove(); closeObj.infoWindow = null; } if (closeObj.timer) { // 인터벌 된 객체가 있는지 clearTimeout(closeObj.timer); } } } //선택 객체 찾기 getSelectObj(array, id) { let idx = array.findIndex(obj => obj.ID.toString() === id.toString()); return array[idx]; } // 마커 보이기 show(type) { this[type].forEach(obj => obj.setVisibleMarker(true)); this[type + 'Flag'] = true; } // 마커 숨기기 hide(type) { this[type].forEach(obj => { obj.setVisibleMarker(false); if (obj.isClick === true) { _MapHandler.close(type, obj.ID); } }); this[type + 'Flag'] = false; } //영상 이벤트 videoEvent(obj) { let video = createVideoJs(obj.ID, obj.URL); obj.timer = setTimeout(()=>{ if (video) { video.pause(); } }, CCTV_DISPLAY_TIME); $('.continue-play').on('click', ()=>{ if (obj.timer) { setTimeout(obj.timer); } if (video) { video.play(); obj.timer = setTimeout(()=>{ video.pause(); }, CCTV_DISPLAY_TIME); } }); } //vms 이미지 표출 이벤트 vmsEvent(obj) { let cnt = 1; if (obj.phaseArray.length > 0) { obj.timer = setInterval(()=>{ if (cnt === obj.phaseArray.length) { cnt = 0; } const activeImage = $('.vms-info-window .content img.active'); if (activeImage[0]) { activeImage.removeClass('active'); } $("#phase-" + obj.phaseArray[cnt]).addClass('active'); cnt++; }, VMS_DISPLAY_TIME); } } }