diff --git a/apps/sn/app/map/page.tsx b/apps/sn/app/map/page.tsx
new file mode 100644
index 000000000..4da5c0d23
--- /dev/null
+++ b/apps/sn/app/map/page.tsx
@@ -0,0 +1,36 @@
+'use client'
+
+import Gis from '@common/business_components/ui/map/Gis';
+import { SearchDiv, SelectBox, UiTable, MainDiv, Title, ContentsDiv, SearchFormLeft, SearchFormRight } from '@common/business_components';
+import ListDiv from '@common/business_components/ui/list_div';
+import { Button, Input } from '@common/components';
+import { useEffect, useState } from "react";
+import { mapService } from "@service";
+
+const Map = () => {
+
+ const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
+
+
+ const fetchData = async () => {
+ try {
+ const response = await mapService.getLyrStngs();
+ setTreeData(response.data); // API 응답 저장
+ } catch (error) {
+ console.error("데이터 불러오기 실패:", error);
+ }
+ };
+
+ // 컴포넌트 마운트 시 1회 실행
+ useEffect(() => {
+ fetchData();
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+export default Map;
diff --git a/apps/sn/app/oper/pplines/page.tsx b/apps/sn/app/oper/pplines/page.tsx
index b5b64b003..48621bddc 100644
--- a/apps/sn/app/oper/pplines/page.tsx
+++ b/apps/sn/app/oper/pplines/page.tsx
@@ -1,5 +1,5 @@
'use client'
-
+import "@common/assets/styles/grid.css";
import { useEffect, useState } from "react";
import { SearchDiv, SelectBox, UiTable, MainDiv, Title, ContentsDiv, SearchFormLeft, SearchFormRight } from '@common/business_components';
import ListDiv from '@common/business_components/ui/list_div';
diff --git a/apps/sn/app/service/index.ts b/apps/sn/app/service/index.ts
index 5052b7321..fda21d232 100644
--- a/apps/sn/app/service/index.ts
+++ b/apps/sn/app/service/index.ts
@@ -1 +1,2 @@
export * from './ppline'
+export * from './map'
diff --git a/apps/sn/app/service/map.ts b/apps/sn/app/service/map.ts
new file mode 100644
index 000000000..062cfeedb
--- /dev/null
+++ b/apps/sn/app/service/map.ts
@@ -0,0 +1,37 @@
+
+import axios from 'axios'
+
+/**
+ * 관망도 API
+ */
+const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`
+
+export type LyrStngProps = {
+ lyr_sn: string
+ lyr_nm: string
+ upper_lyr_sn: string
+ drawOrdr: string
+ flterSql: string
+ gobjTy: string
+ lyrTpNm: string
+ useYn: string
+ minViewSize: string
+ iconNm: string
+ lblAtrbNm: string
+ fontNm: string
+ fontIntnColor: string
+ fontExtnColor: string
+ fontExtnWdth: string
+ lnWdth: string
+ lnColor: string
+ polgnColor: string
+ initEprssYn: string
+ clickPsblYn: string
+}
+
+export const mapService = {
+ //레이어 설정 조회
+ getLyrStngs: () => {
+ return axios.get(`${API_URL}/lyrStngs`)
+ }
+}
diff --git a/apps/sn/app/service/ppline.ts b/apps/sn/app/service/ppline.ts
index 7bc7b4408..33e4c9871 100644
--- a/apps/sn/app/service/ppline.ts
+++ b/apps/sn/app/service/ppline.ts
@@ -2,10 +2,9 @@
import axios from 'axios'
/**
- * 하수관로 API URL
+ * 하수관로 API
*/
-const API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL
-const PPLINES_URL = `${API_URL}/v1/pplines`
+const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/pplines`
const PAGE_NO = 1
const PAGE_SIZE = 20
@@ -16,23 +15,25 @@ export type PplineProps = {
sttyEmd: string
}
-/**
- * 하수관로 서비스
- */
export const pplinesService = {
+ //목록
getPplines: (size: number = PAGE_SIZE, page: number = PAGE_NO) => {
- return axios.get(`${PPLINES_URL}`)
+ return axios.get(`${API_URL}`)
},
+ //상세
getPpline: (swageLnkgPplPk: string) => {
- return axios.get(`${PPLINES_URL}/${swageLnkgPplPk}`)
+ return axios.get(`${API_URL}/${swageLnkgPplPk}`)
},
+ //저장
savePpline: (swageLnkgPplPk: string) => {
- return axios.post(`${PPLINES_URL}`)
+ return axios.post(`${API_URL}`)
},
+ //수정
updatePpline: (swageLnkgPplPk: string) => {
- return axios.put(`${PPLINES_URL}/${swageLnkgPplPk}`)
+ return axios.put(`${API_URL}/${swageLnkgPplPk}`)
},
+ //삭제
removePpline: (swageLnkgPplPk: string) => {
- return axios.delete(`${PPLINES_URL}/${swageLnkgPplPk}`)
+ return axios.delete(`${API_URL}/${swageLnkgPplPk}`)
}
}
diff --git a/apps/sn/next.config.ts b/apps/sn/next.config.ts
index e181f70cf..bc9505fef 100644
--- a/apps/sn/next.config.ts
+++ b/apps/sn/next.config.ts
@@ -1,6 +1,8 @@
import type { NextConfig } from "next";
import webpack from "webpack";
+const geoserverUrl = process.env.NEXT_PUBLIC_GEOSERVER_URL?.replace(/\/$/, '') ?? '';
+
const nextConfig: NextConfig = {
basePath: "/sn",
typescript: {
@@ -40,6 +42,14 @@ const nextConfig: NextConfig = {
return config;
},
+ async rewrites() {
+ return [
+ {
+ source: '/geoserver/:hasudo*',
+ destination: `${geoserverUrl}/:hasudo*`,
+ },
+ ];
+ },
};
export default nextConfig;
diff --git a/apps/sn/package.json b/apps/sn/package.json
index ddae226e0..29f630648 100644
--- a/apps/sn/package.json
+++ b/apps/sn/package.json
@@ -27,7 +27,10 @@
"react-hook-form": "^7.54.2",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2",
- "zustand": "^5.0.3"
+ "zustand": "^5.0.3",
+ "ol": "^7.3.0",
+ "ol-ext": "^4.0.11",
+ "proj4": "^2.9.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
diff --git a/apps/sn/public/images/map/layericon/BML_CBAS_PS.png b/apps/sn/public/images/map/layericon/BML_CBAS_PS.png
new file mode 100644
index 000000000..5ff4add3e
Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_CBAS_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/BML_ELEV_PS.png b/apps/sn/public/images/map/layericon/BML_ELEV_PS.png
new file mode 100644
index 000000000..83970a331
Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_ELEV_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/BML_ETCP_PS.png b/apps/sn/public/images/map/layericon/BML_ETCP_PS.png
new file mode 100644
index 000000000..df9e4957b
Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_ETCP_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/BML_STMS_PS.png b/apps/sn/public/images/map/layericon/BML_STMS_PS.png
new file mode 100644
index 000000000..423f2e8fc
Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_STMS_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/BML_SYMB_PS.png b/apps/sn/public/images/map/layericon/BML_SYMB_PS.png
new file mode 100644
index 000000000..497589bea
Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_SYMB_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_BYST_PS.png b/apps/sn/public/images/map/layericon/RDL_BYST_PS.png
new file mode 100644
index 000000000..1e78cba68
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_BYST_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_CROS_PS.png b/apps/sn/public/images/map/layericon/RDL_CROS_PS.png
new file mode 100644
index 000000000..57b5f2ca5
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_CROS_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_ETCT_PS.png b/apps/sn/public/images/map/layericon/RDL_ETCT_PS.png
new file mode 100644
index 000000000..29c0b77b4
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_ETCT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_OCUP_PS.png b/apps/sn/public/images/map/layericon/RDL_OCUP_PS.png
new file mode 100644
index 000000000..8a1ac0074
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_OCUP_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_RDSN_PS.png b/apps/sn/public/images/map/layericon/RDL_RDSN_PS.png
new file mode 100644
index 000000000..5629de9fb
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_RDSN_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_SCLT_PS.png b/apps/sn/public/images/map/layericon/RDL_SCLT_PS.png
new file mode 100644
index 000000000..7281dc61d
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_SCLT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_SEPC_PS.png b/apps/sn/public/images/map/layericon/RDL_SEPC_PS.png
new file mode 100644
index 000000000..087b4e941
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_SEPC_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_STAT_PS.png b/apps/sn/public/images/map/layericon/RDL_STAT_PS.png
new file mode 100644
index 000000000..13d8ea689
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_STAT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_STLT_PS.png b/apps/sn/public/images/map/layericon/RDL_STLT_PS.png
new file mode 100644
index 000000000..489524b5c
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_STLT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_TFSN_PS.png b/apps/sn/public/images/map/layericon/RDL_TFSN_PS.png
new file mode 100644
index 000000000..6dfff6696
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TFSN_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_TRCT_PS.png b/apps/sn/public/images/map/layericon/RDL_TRCT_PS.png
new file mode 100644
index 000000000..ed1a72d8a
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TRCT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_TREE_PS.png b/apps/sn/public/images/map/layericon/RDL_TREE_PS.png
new file mode 100644
index 000000000..0ee84dedc
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TREE_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_TRSN_PS.png b/apps/sn/public/images/map/layericon/RDL_TRSN_PS.png
new file mode 100644
index 000000000..15675cb31
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TRSN_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png b/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png
new file mode 100644
index 000000000..ffc225159
Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_CLAY_PS.png b/apps/sn/public/images/map/layericon/SWL_CLAY_PS.png
new file mode 100644
index 000000000..e603ffa9c
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_CLAY_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_CLAY_PS_RED.png b/apps/sn/public/images/map/layericon/SWL_CLAY_PS_RED.png
new file mode 100644
index 000000000..0e5450688
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_CLAY_PS_RED.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_DEPT_PS.png b/apps/sn/public/images/map/layericon/SWL_DEPT_PS.png
new file mode 100644
index 000000000..fede6c304
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_DEPT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS.png
new file mode 100644
index 000000000..75217f494
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLACK.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLACK.png
new file mode 100644
index 000000000..5d2e8a70a
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLACK.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLUE.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLUE.png
new file mode 100644
index 000000000..e521cb84a
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLUE.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_GRAY.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_GRAY.png
new file mode 100644
index 000000000..e19f3d8b3
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_GRAY.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_GREEN.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_GREEN.png
new file mode 100644
index 000000000..4503c13c6
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_GREEN.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_RED.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_RED.png
new file mode 100644
index 000000000..b8fa45f9c
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_RED.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_YELLOW.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_YELLOW.png
new file mode 100644
index 000000000..b7de1fbfd
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_YELLOW.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_SPEW_PS.png b/apps/sn/public/images/map/layericon/SWL_SPEW_PS.png
new file mode 100644
index 000000000..d260ea3a6
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPEW_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS.png
new file mode 100644
index 000000000..84c7992e4
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLACK.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLACK.png
new file mode 100644
index 000000000..fe67f8f98
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLACK.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLUE.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLUE.png
new file mode 100644
index 000000000..691f2f45e
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLUE.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREEN.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREEN.png
new file mode 100644
index 000000000..d09c4bd97
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREEN.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREY.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREY.png
new file mode 100644
index 000000000..32d0bf519
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREY.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_RED.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_RED.png
new file mode 100644
index 000000000..a2da36f9b
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_RED.png differ
diff --git a/apps/sn/public/images/map/layericon/SWL_VENT_PS.png b/apps/sn/public/images/map/layericon/SWL_VENT_PS.png
new file mode 100644
index 000000000..834eeec21
Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_VENT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/TL_SPBD_ENTRC.png b/apps/sn/public/images/map/layericon/TL_SPBD_ENTRC.png
new file mode 100644
index 000000000..8e7de8593
Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPBD_ENTRC.png differ
diff --git a/apps/sn/public/images/map/layericon/TL_SPGF_LOC.png b/apps/sn/public/images/map/layericon/TL_SPGF_LOC.png
new file mode 100644
index 000000000..74b0adac3
Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPGF_LOC.png differ
diff --git a/apps/sn/public/images/map/layericon/TL_SPOT_NOIN.png b/apps/sn/public/images/map/layericon/TL_SPOT_NOIN.png
new file mode 100644
index 000000000..4800beaeb
Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPOT_NOIN.png differ
diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_BANO.png b/apps/sn/public/images/map/layericon/TL_SPRD_BANO.png
new file mode 100644
index 000000000..8d08dca29
Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_BANO.png differ
diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_CRSRD.png b/apps/sn/public/images/map/layericon/TL_SPRD_CRSRD.png
new file mode 100644
index 000000000..57b5f2ca5
Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_CRSRD.png differ
diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_NONM.png b/apps/sn/public/images/map/layericon/TL_SPRD_NONM.png
new file mode 100644
index 000000000..e68c05ab6
Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_NONM.png differ
diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_RENM.png b/apps/sn/public/images/map/layericon/TL_SPRD_RENM.png
new file mode 100644
index 000000000..4800beaeb
Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_RENM.png differ
diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_RWNM.png b/apps/sn/public/images/map/layericon/TL_SPRD_RWNM.png
new file mode 100644
index 000000000..e68c05ab6
Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_RWNM.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_BMAN_PS.png b/apps/sn/public/images/map/layericon/UFL_BMAN_PS.png
new file mode 100644
index 000000000..f2f64a641
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BMAN_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_BOPC_PS.png b/apps/sn/public/images/map/layericon/UFL_BOPC_PS.png
new file mode 100644
index 000000000..0f8e99f45
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BOPC_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_BPIP_PS.png b/apps/sn/public/images/map/layericon/UFL_BPIP_PS.png
new file mode 100644
index 000000000..dcc3622a7
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BPIP_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_BPOL_PS.png b/apps/sn/public/images/map/layericon/UFL_BPOL_PS.png
new file mode 100644
index 000000000..4f1102989
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BPOL_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_BTRS_PS.png b/apps/sn/public/images/map/layericon/UFL_BTRS_PS.png
new file mode 100644
index 000000000..557c17089
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BTRS_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_GMNT_PS.png b/apps/sn/public/images/map/layericon/UFL_GMNT_PS.png
new file mode 100644
index 000000000..6081d8b00
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_GMNT_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_GPIP_PS.png b/apps/sn/public/images/map/layericon/UFL_GPIP_PS.png
new file mode 100644
index 000000000..5d7e86fd2
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_GPIP_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_GPRS_PS.png b/apps/sn/public/images/map/layericon/UFL_GPRS_PS.png
new file mode 100644
index 000000000..79c120f20
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_GPRS_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_GVAL_PS.png b/apps/sn/public/images/map/layericon/UFL_GVAL_PS.png
new file mode 100644
index 000000000..e8f7cde2e
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_GVAL_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_KMAN_PS.png b/apps/sn/public/images/map/layericon/UFL_KMAN_PS.png
new file mode 100644
index 000000000..764ce8ba5
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_KMAN_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_KPOL_PS.png b/apps/sn/public/images/map/layericon/UFL_KPOL_PS.png
new file mode 100644
index 000000000..d9fa712cf
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_KPOL_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/UFL_VALV_PS.png b/apps/sn/public/images/map/layericon/UFL_VALV_PS.png
new file mode 100644
index 000000000..1b5d70178
Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_VALV_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/WTL_FIRE_PS.png b/apps/sn/public/images/map/layericon/WTL_FIRE_PS.png
new file mode 100644
index 000000000..4137a3f74
Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_FIRE_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/WTL_FLOW_PS.png b/apps/sn/public/images/map/layericon/WTL_FLOW_PS.png
new file mode 100644
index 000000000..285f7edfa
Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_FLOW_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/WTL_MANH_PS.png b/apps/sn/public/images/map/layericon/WTL_MANH_PS.png
new file mode 100644
index 000000000..5f2a57cfe
Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_MANH_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/WTL_META_PS.png b/apps/sn/public/images/map/layericon/WTL_META_PS.png
new file mode 100644
index 000000000..22c7af31b
Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_META_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/WTL_PIPE_PS.png b/apps/sn/public/images/map/layericon/WTL_PIPE_PS.png
new file mode 100644
index 000000000..c9c6863f6
Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_PIPE_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/WTL_RSRV_PS.png b/apps/sn/public/images/map/layericon/WTL_RSRV_PS.png
new file mode 100644
index 000000000..d7b307a61
Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_RSRV_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/WTL_RSRV_PS_GRAY.png b/apps/sn/public/images/map/layericon/WTL_RSRV_PS_GRAY.png
new file mode 100644
index 000000000..33a667a1e
Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_RSRV_PS_GRAY.png differ
diff --git a/apps/sn/public/images/map/layericon/WTL_VALV_PS.png b/apps/sn/public/images/map/layericon/WTL_VALV_PS.png
new file mode 100644
index 000000000..ca2b4788b
Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_VALV_PS.png differ
diff --git a/apps/sn/public/images/map/layericon/default.png b/apps/sn/public/images/map/layericon/default.png
new file mode 100644
index 000000000..92bf1f587
Binary files /dev/null and b/apps/sn/public/images/map/layericon/default.png differ
diff --git a/apps/sn/public/images/map/layericon/reset.png b/apps/sn/public/images/map/layericon/reset.png
new file mode 100644
index 000000000..45f889206
Binary files /dev/null and b/apps/sn/public/images/map/layericon/reset.png differ
diff --git a/apps/sn/public/images/map/toolbar/arrow_down.png b/apps/sn/public/images/map/toolbar/arrow_down.png
new file mode 100644
index 000000000..88590ecaf
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/arrow_down.png differ
diff --git a/apps/sn/public/images/map/toolbar/arrow_right.png b/apps/sn/public/images/map/toolbar/arrow_right.png
new file mode 100644
index 000000000..7090c37dd
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/arrow_right.png differ
diff --git a/apps/sn/public/images/map/toolbar/backward_icon.png b/apps/sn/public/images/map/toolbar/backward_icon.png
new file mode 100644
index 000000000..3a853475e
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/backward_icon.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_area.png b/apps/sn/public/images/map/toolbar/btn_cont_area.png
new file mode 100644
index 000000000..044562b55
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_area.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_area_on.png b/apps/sn/public/images/map/toolbar/btn_cont_area_on.png
new file mode 100644
index 000000000..0129ff1bd
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_area_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_next.png b/apps/sn/public/images/map/toolbar/btn_cont_next.png
new file mode 100644
index 000000000..075111dcf
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_next.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_next_on.png b/apps/sn/public/images/map/toolbar/btn_cont_next_on.png
new file mode 100644
index 000000000..4fdbe3a00
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_next_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_prev.png b/apps/sn/public/images/map/toolbar/btn_cont_prev.png
new file mode 100644
index 000000000..7e3fae7d2
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_prev.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_prev_on.png b/apps/sn/public/images/map/toolbar/btn_cont_prev_on.png
new file mode 100644
index 000000000..fb2d55cfd
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_prev_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_print.png b/apps/sn/public/images/map/toolbar/btn_cont_print.png
new file mode 100644
index 000000000..82596b83d
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_print.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_print_on.png b/apps/sn/public/images/map/toolbar/btn_cont_print_on.png
new file mode 100644
index 000000000..bb7f27334
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_print_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_ruler.png b/apps/sn/public/images/map/toolbar/btn_cont_ruler.png
new file mode 100644
index 000000000..501e460a4
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_ruler.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_ruler_on.png b/apps/sn/public/images/map/toolbar/btn_cont_ruler_on.png
new file mode 100644
index 000000000..676a81bcc
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_ruler_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_save.png b/apps/sn/public/images/map/toolbar/btn_cont_save.png
new file mode 100644
index 000000000..c057b9377
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_save.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_save_on.png b/apps/sn/public/images/map/toolbar/btn_cont_save_on.png
new file mode 100644
index 000000000..bf8e6d623
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_save_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_sketch.png b/apps/sn/public/images/map/toolbar/btn_cont_sketch.png
new file mode 100644
index 000000000..38a5f507f
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_sketch.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_sketch_on.png b/apps/sn/public/images/map/toolbar/btn_cont_sketch_on.png
new file mode 100644
index 000000000..79fb54e03
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_sketch_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_zoomin.png b/apps/sn/public/images/map/toolbar/btn_cont_zoomin.png
new file mode 100644
index 000000000..ac32fbab0
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_zoomin.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_zoomin_on.png b/apps/sn/public/images/map/toolbar/btn_cont_zoomin_on.png
new file mode 100644
index 000000000..f258a965f
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_zoomin_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_zoomout.png b/apps/sn/public/images/map/toolbar/btn_cont_zoomout.png
new file mode 100644
index 000000000..2ee0b5161
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_zoomout.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_cont_zoomout_on.png b/apps/sn/public/images/map/toolbar/btn_cont_zoomout_on.png
new file mode 100644
index 000000000..58dcbfdae
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_zoomout_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_add.png b/apps/sn/public/images/map/toolbar/btn_edit_add.png
new file mode 100644
index 000000000..fe4f0993f
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_add.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_add_on.png b/apps/sn/public/images/map/toolbar/btn_edit_add_on.png
new file mode 100644
index 000000000..37a836298
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_add_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_del.png b/apps/sn/public/images/map/toolbar/btn_edit_del.png
new file mode 100644
index 000000000..3bb372ab9
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_del.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_del_on.png b/apps/sn/public/images/map/toolbar/btn_edit_del_on.png
new file mode 100644
index 000000000..89e558739
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_del_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_next.png b/apps/sn/public/images/map/toolbar/btn_edit_next.png
new file mode 100644
index 000000000..df4913c90
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_next.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_next_on.png b/apps/sn/public/images/map/toolbar/btn_edit_next_on.png
new file mode 100644
index 000000000..4e5540b55
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_next_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_prev.png b/apps/sn/public/images/map/toolbar/btn_edit_prev.png
new file mode 100644
index 000000000..e94bf6d42
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_prev.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_prev_on.png b/apps/sn/public/images/map/toolbar/btn_edit_prev_on.png
new file mode 100644
index 000000000..8645dedfa
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_prev_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_save.png b/apps/sn/public/images/map/toolbar/btn_edit_save.png
new file mode 100644
index 000000000..5f01dd55c
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_save.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_save_on.png b/apps/sn/public/images/map/toolbar/btn_edit_save_on.png
new file mode 100644
index 000000000..b035687d6
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_save_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_slt.png b/apps/sn/public/images/map/toolbar/btn_edit_slt.png
new file mode 100644
index 000000000..aaf2bbd08
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_slt.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_slt_on.png b/apps/sn/public/images/map/toolbar/btn_edit_slt_on.png
new file mode 100644
index 000000000..02d035a74
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_slt_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_snap.png b/apps/sn/public/images/map/toolbar/btn_edit_snap.png
new file mode 100644
index 000000000..fc91d4304
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_snap.png differ
diff --git a/apps/sn/public/images/map/toolbar/btn_edit_snap_on.png b/apps/sn/public/images/map/toolbar/btn_edit_snap_on.png
new file mode 100644
index 000000000..f856d8fbe
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_snap_on.png differ
diff --git a/apps/sn/public/images/map/toolbar/close_icon.png b/apps/sn/public/images/map/toolbar/close_icon.png
new file mode 100644
index 000000000..e8e76d5d7
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/close_icon.png differ
diff --git a/apps/sn/public/images/map/toolbar/ico_gis_sprite.png b/apps/sn/public/images/map/toolbar/ico_gis_sprite.png
new file mode 100644
index 000000000..0ee4b5985
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/ico_gis_sprite.png differ
diff --git a/apps/sn/public/images/map/toolbar/icon-viewclose.png b/apps/sn/public/images/map/toolbar/icon-viewclose.png
new file mode 100644
index 000000000..d93ce9c05
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/icon-viewclose.png differ
diff --git a/apps/sn/public/images/map/toolbar/img_base_map.png b/apps/sn/public/images/map/toolbar/img_base_map.png
new file mode 100644
index 000000000..7f96fcf5e
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/img_base_map.png differ
diff --git a/apps/sn/public/images/map/toolbar/img_network_map.png b/apps/sn/public/images/map/toolbar/img_network_map.png
new file mode 100644
index 000000000..df80af6dc
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/img_network_map.png differ
diff --git a/apps/sn/public/images/map/toolbar/img_road_map.png b/apps/sn/public/images/map/toolbar/img_road_map.png
new file mode 100644
index 000000000..02d015d12
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/img_road_map.png differ
diff --git a/apps/sn/public/images/map/toolbar/img_settle_map.png b/apps/sn/public/images/map/toolbar/img_settle_map.png
new file mode 100644
index 000000000..1a93f50e8
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/img_settle_map.png differ
diff --git a/apps/sn/public/images/map/toolbar/loading.gif b/apps/sn/public/images/map/toolbar/loading.gif
new file mode 100644
index 000000000..7c3683bb4
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/loading.gif differ
diff --git a/apps/sn/public/images/map/toolbar/map_pin.png b/apps/sn/public/images/map/toolbar/map_pin.png
new file mode 100644
index 000000000..b6cc2f6aa
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/map_pin.png differ
diff --git a/apps/sn/public/images/map/toolbar/toggle_off.png b/apps/sn/public/images/map/toolbar/toggle_off.png
new file mode 100644
index 000000000..d5dea9f67
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/toggle_off.png differ
diff --git a/apps/sn/public/images/map/toolbar/toggle_on.png b/apps/sn/public/images/map/toolbar/toggle_on.png
new file mode 100644
index 000000000..9cb573875
Binary files /dev/null and b/apps/sn/public/images/map/toolbar/toggle_on.png differ
diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js
new file mode 100644
index 000000000..d0fbefe2a
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/Gis.js
@@ -0,0 +1,159 @@
+import './lib/css/gis.common.css';
+import React, { createContext, useContext, useEffect, useMemo, useRef, useState, useCallback } from 'react';
+import { Tabs, TabsList, TabsTrigger, TabsContent } from '@common/components/ui/tabs';
+import OlMap from 'ol/Map';
+import View from 'ol/View';
+import { defaults as defaultControls, OverviewMap } from 'ol/control';
+import { defaults } from 'ol/interaction';
+import * as olProj from 'ol/proj';
+import proj4 from 'proj4';
+import { register } from 'ol/proj/proj4';
+import GisApp from './lib/gis.core';
+import GisContent from './GisContent';
+import GisTree from './GisTree';
+import GisLonLat from './GisLonLat';
+import GisToolBar from './GisToolBar';
+import GisSearch from './GisSearch';
+import MapToggleBtn from './MapToggleBtn';
+
+export const GisContext = createContext({gisCore: null, gisMap: null, treeData:null, checkedLayerIds:null});
+export const useGisContext = () => useContext(GisContext);
+
+function Gis({ treeData = [] }) {
+ const mapRef = useRef();
+ const overviewMapRef = useRef(null);
+ const [mapObject, setMapObject] = useState({});
+ const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []);
+ //const [treeData, setTreeData] = useState([]);
+ const checkedLayerIds = useRef([]);
+ let gisMap = null;
+
+ useEffect(() => {
+ if (!mapRef.current) return;
+
+ // EPSG:5179 등록
+ proj4.defs([
+ [
+ 'EPSG:5179',
+ '+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
+ ],
+ ]);
+ register(proj4);
+
+ //맵 생성성
+ const map = new OlMap({
+ layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']],
+ renderer: 'webgl',
+ target: mapRef.current,
+ interactions: defaults({ doubleClickZoom: false }),
+ controls: defaultControls({
+ zoom: false,
+ rotate: false,
+ attributionOptions: {
+ collapsible: false,
+ },
+ }).extend([]),
+ view: new View({
+ projection: GisApp.TO_PROJ,
+ zoom: gisCore.defaults.zoom,
+ center: olProj.transform(
+ [126.85306, 35.571634],
+ GisApp.FROM_PROJ,
+ GisApp.TO_PROJ
+ ),
+ minZoom: gisCore.defaults.minZoom,
+ maxZoom: gisCore.defaults.maxZoom,
+ }),
+ });
+
+ //미니맵
+ const overviewMap = new OverviewMap({
+ layers: [GisApp.Module.core.baseMiniMapLayers['base']],
+ view: new View({
+ projection: GisApp.TO_PROJ,
+ minZoom: 9,
+ }),
+ collapsed: false,
+ collapseLabel: '\u00BB',
+ label: '\u00AB',
+ });
+
+ map.addControl(overviewMap);
+ gisCore.__proto__.map = map;
+
+ gisCore.scaleControl();
+ gisCore.changeBaseLayer('base');
+ setMapObject({ map: map });
+ }, []);
+
+ useEffect(() => {
+ //if (!locgovCd) return null;
+ if (!treeData || treeData.length === 0) return;
+
+ gisCore.deleteAllOverlay();
+ gisCore.deleteAllLayer();
+
+ const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn');
+ GisApp.layerCode = layerCode;
+
+ //setTreeData(data);
+ const layerItems = getLayerItems(layerCode);
+ //setEditLayerItems(prev => [...layerItems]);
+ }, [treeData]);
+
+ const getLayerItems= (layerCode) => {
+ const layerItems = Object.entries(layerCode)
+ .filter(([key, obj], idx) => obj?.updtPsblYn === 'Y')
+ .map(([key, obj]) => ({
+ ...obj,
+ name: obj.lyrNm,
+ label: obj.lyrNm,
+ value: obj.lyrSn + '',
+ }))
+ .sort((a, b) => {
+ if (a.sortOrdr > b.sortOrdr) return 1;
+ if (a.sortOrdr < b.sortOrdr) return -1;
+ return 0;
+ });
+ return layerItems;
+ };
+
+ if (mapObject.map) {
+ try {
+ gisMap = mapObject.map;
+ } catch (e) {
+ console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력
+ }
+ }
+
+ const [isSidebarOpen, setIsSidebarOpen] = useState(true);
+
+ return (
+
+ <>
+
+
+
+
+ 레이어
+ 시설검색
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+
+ );
+}
+
+export default React.memo(Gis);
diff --git a/packages/common/business_components/src/ui/map/GisContent.js b/packages/common/business_components/src/ui/map/GisContent.js
new file mode 100644
index 000000000..2179a53d8
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/GisContent.js
@@ -0,0 +1,61 @@
+import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
+import GisApp from './lib/gis.core';
+import { useGisContext } from './Gis';
+import GisLonLat from './GisLonLat';
+import GisSelect from './GisSelect';
+import GisToolBar from './GisToolBar';
+import './lib/css/gis.edit.css';
+import MapToggleBtn from './MapToggleBtn';
+//import GisEdit from './GisEdit';
+
+export default function GisContent({ edit = false }) {
+ const { gisMap, gisCore } = useGisContext();
+ const [visibleComponents, setVisibleComponents] = useState({});
+ const addressResultLayer = useRef(null);
+ const [playShow, setPlayShow] = useState({ show: false });
+
+ //셀렉트 버튼 이벤트
+ const clickFeature = useCallback(feature => {
+ if (feature) {
+ const layer = feature.getLayer();
+ setVisibleComponents(prevState => ({
+ ...prevState,
+ }));
+ }
+ }, []);
+
+ const selectRslt = useCallback(feature => {
+ GisApp.Module.layer.highLightFeature(
+ [feature],
+ {
+ id: GisApp.hlLayerId,
+ zIndex: 10,
+ opacity: 1,
+ selectable: true,
+ },
+ { width: 30 }
+ );
+ clickFeature(feature);
+ }, []);
+
+ useEffect(() => {
+ if (gisMap) {
+ setVisibleComponents(prevState => ({
+ ...prevState,
+ }));
+ }
+ }, [gisMap]);
+
+ if (!gisMap) return null;
+
+ return (
+ <>
+ {/**/}
+ {/* */}
+
+
+
+ {/* {edit && } */}
+ >
+ );
+}
diff --git a/packages/common/business_components/src/ui/map/GisEdit.js b/packages/common/business_components/src/ui/map/GisEdit.js
new file mode 100644
index 000000000..7c961c865
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/GisEdit.js
@@ -0,0 +1,173 @@
+import GisApp from './lib/gis.core';
+import React, { useContext, useEffect, useRef, useState } from 'react';
+import { GisContext, setTree } from './Gis';
+import Button from '../button/Button';
+import { gAlert, gConfirm } from './lib/util';
+import { fclApi } from '../../js/apis/fcl/fclApi';
+
+export default React.memo(function GisEdit() {
+ const { editLayerItems = [], siteSn, locgovCd, checkedLayerIds } = useContext(GisContext);
+ const editModule = GisApp.Module.edit;
+ const ftrIdn = useRef(null);
+ const selectedLyrId = useRef(null);
+ if (editLayerItems.length > 0 && !selectedLyrId.current) {
+ selectedLyrId.current = editLayerItems[0].value;
+ }
+ const [btnEvt, setBtnEvt] = useState({ edit: false, modify: false, add: false, snap: false });
+ const edit = () => {
+ const edit = !btnEvt.edit;
+ if (edit) {
+ setBtnEvt({ ...btnEvt, edit: edit });
+ } else {
+ setBtnEvt({ edit: edit, modify: false, add: false, snap: false });
+ editModule.clearUndoRedoFeature();
+ }
+ editModule.removeInteraction();
+ };
+
+ const modify = () => {
+ const modify = !btnEvt.modify;
+ setBtnEvt({ ...btnEvt, ...{ add: false }, modify });
+ if (modify) {
+ editModule.setModify();
+ } else {
+ editModule.removeModify();
+ }
+ };
+
+ const add = () => {
+ let add = !btnEvt.add;
+ if (add) {
+ if (!checkedLayerIds?.current?.includes(selectedLyrId.current)) {
+ alert('트리에서 레이어 체크 후 추가 가능합니다.');
+ return;
+ }
+ const errMsg = editModule.setDraw(selectedLyrId.current);
+ if (errMsg) {
+ alert(errMsg);
+ return;
+ }
+ } else {
+ editModule.removeDraw();
+ }
+ setBtnEvt({ ...btnEvt, ...{ modify: false }, add });
+ };
+
+ const del = () => {
+ editModule.deleteFeature();
+ };
+
+ const undo = () => {
+ GisApp.Module.edit.undo();
+ };
+
+ const redo = () => {
+ GisApp.Module.edit.redo();
+ };
+
+ const snap = () => {
+ const snap = !btnEvt.snap;
+ setBtnEvt({ ...btnEvt, snap });
+ if (snap) {
+ editModule.setSnap();
+ } else {
+ editModule.removeSnap();
+ }
+ };
+
+ const validateEditFeatures = editFeatures => {
+ if (editFeatures.length === 0) {
+ //gAlert('INFO', 'NO_CHANGED_DATA');
+ return false;
+ }
+ return true;
+ };
+
+ const save = async () => {
+ const editFeatureInfo = editModule.getEditFeatureInfo();
+ const editFeatures = Object.values(editFeatureInfo).map(value => ({
+ siteSn,
+ locgovCd,
+ geom: value.geom,
+ flag: value.flag,
+ lyrId: value.lyr_id,
+ }));
+ if (!validateEditFeatures(editFeatures)) {
+ return;
+ }
+ if (!gConfirm('CONFIRM', 'SAVE_QUEST')) return false;
+ const result = await fclApi.saveEditFeatures(editFeatures);
+ if (result.code === 200) {
+ editModule.clearUndoRedo();
+ }
+ };
+
+ useEffect(() => {
+ //setBtnEvt({ edit: false, modify: false, add: false, snap: false });
+ editModule.setUndoInteraction();
+ editModule.removeInteraction();
+ }, [locgovCd]);
+ if (editLayerItems.length === 0) return;
+ return (
+ <>
+
+
+
+
+ {btnEvt.edit && (
+
+ -
+
+ 수정
+
+
+ -
+
+ 추가
+
+
+ -
+
+ 삭제
+
+
+ -
+
+ 이전
+
+
+ -
+
+ 다음
+
+
+ -
+
+ 스냅
+
+
+ -
+
+ 저장
+
+
+
+ )}
+
+ >
+ );
+});
diff --git a/packages/common/business_components/src/ui/map/GisLonLat.js b/packages/common/business_components/src/ui/map/GisLonLat.js
new file mode 100644
index 000000000..2cee2a9c8
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/GisLonLat.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import GisApp from './lib/gis.core';
+import { MousePosition } from 'ol/control';
+import { useContext, useEffect, useRef } from 'react';
+import { GisContext } from './Gis';
+export default React.memo(function GisLonLat() {
+ const { gisCore } = useContext(GisContext);
+ const labelLonLatRef = useRef(null);
+
+ useEffect(() => {
+ if (!gisCore?.map || !labelLonLatRef.current) return;
+
+ // 기존 MousePosition 제거를 위한 컨트롤 추적
+ let existingMouseControls = gisCore.map
+ .getControls()
+ .getArray()
+ .filter(control => control instanceof MousePosition);
+
+ // 기존 MousePosition 컨트롤 제거
+ existingMouseControls.forEach(control => {
+ gisCore.map.removeControl(control);
+ });
+
+ // 새로운 MousePosition 컨트롤 추가
+ const mousePosition = new MousePosition({
+ coordinateFormat: function (coordinate) {
+ let lng = coordinate[0];
+ let lat = coordinate[1];
+ return `경도 : ${lng.toFixed(3)} 위도 : ${lat.toFixed(3)}`;
+ },
+ projection: GisApp.FROM_PROJ,
+ className: 'custom-mouse-position',
+ target: labelLonLatRef.current,
+ });
+ gisCore.map.addControl(mousePosition);
+ }, [gisCore.map]);
+ return (
+ <>
+
+ >
+ );
+});
diff --git a/packages/common/business_components/src/ui/map/GisSearch.js b/packages/common/business_components/src/ui/map/GisSearch.js
new file mode 100644
index 000000000..c321d8efa
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/GisSearch.js
@@ -0,0 +1,9 @@
+import React from 'react';
+
+export default function GisSearch() {
+ return (
+
+
범례 영역입니다 (향후 컴포넌트로 교체 가능)
+
+ );
+}
\ No newline at end of file
diff --git a/packages/common/business_components/src/ui/map/GisSelect.js b/packages/common/business_components/src/ui/map/GisSelect.js
new file mode 100644
index 000000000..85354d8b5
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/GisSelect.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import GisApp from './lib/gis.core';
+import GisOlSelect from './lib/gis.select';
+import { useContext } from 'react';
+import { GisContext } from './Gis';
+export default React.memo(function GisSelect({ clickFeature }) {
+ const { gisMap } = useContext(GisContext);
+ const select = new GisOlSelect(gisMap);
+ GisApp.Module.select = select;
+ const reset = () => {
+ select.reset();
+ };
+
+ select.getSelectedInfo(function (features) {
+ clickFeature(features[0]);
+ });
+ return (
+ <>
+
+
선,면,다각형을 이용한 시설물 선택 영역
+
+ -
+
+ 단일선택
+
+
+ -
+
+ 새로고침
+
+
+
+
+ >
+ );
+});
diff --git a/packages/common/business_components/src/ui/map/GisToolBar.js b/packages/common/business_components/src/ui/map/GisToolBar.js
new file mode 100644
index 000000000..c5c613cc9
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/GisToolBar.js
@@ -0,0 +1,71 @@
+import React from 'react';
+import GisApp from './lib/gis.core';
+import GisOlMeasure from './lib/gis.measure';
+import { useContext } from 'react';
+import { useGisContext } from './Gis';
+export default React.memo(function GisToolBar() {
+ const { gisMap } = useGisContext();
+ if (!gisMap) return <>>;
+ const gisMeasuer = new GisOlMeasure(gisMap);
+ GisApp.Module.measure = gisMeasuer;
+ const zoomIn = () => {
+ if (gisMap.getView().getMaxZoom() > gisMap.getView().getZoom()) {
+ gisMap.getView().animate({
+ zoom: gisMap.getView().getZoom() + 1,
+ duration: 250,
+ });
+ }
+ };
+ const zoomOut = () => {
+ if (gisMap.getView().getMaxZoom() > gisMap.getView().getZoom()) {
+ gisMap.getView().animate({
+ zoom: gisMap.getView().getZoom() - 1,
+ duration: 250,
+ });
+ }
+ };
+
+ const measure = type => {
+ gisMeasuer.addInteraction(type);
+ };
+
+ const reset = () => {
+ gisMeasuer.removeFeature();
+ };
+ return (
+ <>
+
+ >
+ );
+});
diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js
new file mode 100644
index 000000000..487e8bfec
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/GisTree.js
@@ -0,0 +1,234 @@
+import GisApp from './lib/gis.core';
+import { util } from './lib/util';
+import React, { useEffect, useState } from 'react';
+import { useGisContext } from './Gis';
+import { DndProvider } from "react-dnd";
+import {
+ Tree,
+ MultiBackend,
+ getBackendOptions
+} from "@minoru/react-dnd-treeview";
+import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components/ui/tabs";
+
+export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => {} }) {
+ const { gisCore, treeData, checkedLayerIds } = useGisContext();
+ const [checked, setChecked] = useState([]);
+ const [expanded, setExpanded] = useState([]);
+ const [nodes, setNodes] = useState([]);
+ const [isSidebarOpen, setIsSidebarOpen] = useState(true);
+
+ useEffect(() => {
+ if (treeData && treeData.length > 0) {
+ const nodeData = setNodeValues(treeData);
+ setNodes(nodeData);
+ gisCore.setAllLayersOnMap(false);
+ }
+ console.log(treeData)
+ }, [treeData]);
+
+ useEffect(() => {
+ if (nodes.length > 0) {
+ const defaultChecked = getDefaultCheckedNodes(nodes);
+ setChecked(defaultChecked);
+ const defaultExpanded = Array.from(new Set([
+ ...defaultChecked,
+ ...defaultChecked.flatMap((id) => getAncestors(id, nodes))
+ ]));
+ setExpanded(defaultExpanded);
+ gisCore.setLayerOnMap(defaultChecked, true);
+ }
+ console.log(nodes)
+ }, [nodes]);
+
+ const getDefaultCheckedNodes = (nodes, values = []) => {
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
+ if (node.data.initEprssYn === 'Y') {
+ values.push(node.data.lyrSn);
+ checkedLayerIds?.current.push(node.data.lyrSn + '');
+ }
+ }
+ return values;
+ };
+
+ const getDescendants = (id, all) => {
+ const children = all.filter((n) => n.parent === id);
+ return children.reduce(
+ (acc, child) => acc.concat(child.id, ...getDescendants(child.id, all)),
+ []
+ );
+ };
+
+ const getAncestors = (id, all) => {
+ const node = all.find((n) => n.id === id);
+ if (!node || node.parent == null) return [];
+ return [node.parent, ...getAncestors(node.parent, all)];
+ };
+
+ const isEveryChildChecked = (parentId, all, checked) => {
+ const children = all.filter((n) => n.parent === parentId);
+ return children.every((c) => checked.includes(c.id));
+ };
+
+ const handleCheck = (id) => {
+ setChecked((prevChecked) => {
+ const isChecked = prevChecked.includes(id);
+ let newChecked = [...prevChecked];
+ if (isChecked) {
+ const descendants = getDescendants(id, nodes);
+ newChecked = newChecked.filter((cid) => cid !== id && !descendants.includes(cid));
+ const ancestors = getAncestors(id, nodes);
+ ancestors.forEach((p) => {
+ if (newChecked.includes(p)) {
+ newChecked = newChecked.filter((cid) => cid !== p);
+ }
+ });
+ } else {
+ const descendants = getDescendants(id, nodes);
+ newChecked = Array.from(new Set([...newChecked, id, ...descendants]));
+ const ancestors = getAncestors(id, nodes);
+ ancestors.forEach((p) => {
+ if (isEveryChildChecked(p, nodes, newChecked)) {
+ newChecked.push(p);
+ }
+ });
+ // setExpanded((prev) => Array.from(new Set([...prev, ...ancestors])));
+ }
+ const allIds = nodes.map((n) => n.id);
+ const unchecked = allIds.filter((nid) => !newChecked.includes(nid));
+ checkedLayerIds.current = newChecked;
+ gisCore.setLayerOnMap(newChecked, true);
+ gisCore.setLayerOnMap(unchecked, false);
+ return newChecked;
+ });
+ };
+
+ const setNodeValues = (result) => {
+ return result.map((item) => {
+ const src = makeLegendSrc(item);
+ let label = item.lyrNm;
+ return {
+ id: item.lyrSn,
+ text: label,
+ parent: item.upperLyrSn,
+ droppable: !!item.children,
+ data: item,
+ icon: src ?
: '',
+ };
+ });
+ };
+
+ const findUpLyrId = (nodes, targetLyrId) => {
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
+ if (node.data && node.data.lyrSn === targetLyrId) {
+ return node.data.upperLyrSn;
+ }
+ if (node.children) {
+ const upLyrId = findUpLyrId(node.children, targetLyrId);
+ if (upLyrId) {
+ return upLyrId;
+ }
+ }
+ }
+ return null;
+ };
+
+ const makeLegendSrc = layerinfo => {
+ if(!layerinfo.gobjTy) return null;
+
+ let src;
+ if (layerinfo.iconNm) {
+ src = GisApp.CONTEXT_LAYER_PATH + layerinfo.iconNm;
+ } else {
+ const ss = makeLegendImage(layerinfo);
+ src = ss.src;
+ }
+ return src;
+ };
+
+ const makeLegendImage = layerinfo => {
+ let image = document.createElement('img');
+ let canvas = document.createElement('canvas');
+ canvas.width = 16;
+ canvas.height = 16;
+ let ctx = canvas.getContext('2d');
+ ctx.beginPath();
+
+ if (layerinfo.gobjTy === 'lineLabelPoligon') {
+ ctx.moveTo(1, 1);
+ ctx.lineTo(15, 1);
+ ctx.lineTo(15, 15);
+ ctx.lineTo(1, 15);
+ ctx.lineTo(1, 1);
+ } else if (layerinfo.gobjTy === 'line') {
+ ctx.moveTo(1, 15);
+ ctx.lineTo(5, 1);
+ ctx.lineTo(9, 15);
+ ctx.lineTo(15, 1);
+ } else {
+ ctx.moveTo(1, 1);
+ ctx.lineTo(15, 1);
+ ctx.lineTo(15, 15);
+ ctx.lineTo(1, 15);
+ ctx.lineTo(1, 1);
+ }
+
+ if (layerinfo.gobjTy === 'lineLabelPoligon') {
+ ctx.fillStyle = layerinfo.polgnColor;
+ ctx.fill();
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = layerinfo.lnColor;
+ ctx.stroke();
+ }
+
+ if (layerinfo.gobjTy === 'line') {
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = layerinfo.lnColor;
+ ctx.stroke();
+ }
+
+ image.src = canvas.toDataURL('image/png');
+ return image;
+ };
+
+ if (!treeData || treeData.length === 0) return null;
+
+ return (
+ <>
+
+ (
+
+ {node.droppable && (
+
+ )}
+ handleCheck(node.id)}
+ className="cursor-pointer"
+ />
+ {node.icon && {node.icon}}
+ {node.text}
+
+ )}
+ sort={(a, b) => {
+ const aOrder = parseInt(a.data?.sortOrdr ?? '0');
+ const bOrder = parseInt(b.data?.sortOrdr ?? '0');
+ return aOrder - bOrder;
+ }}
+ />
+
+ >
+ );
+});
diff --git a/packages/common/business_components/src/ui/map/Google3DMap.js b/packages/common/business_components/src/ui/map/Google3DMap.js
new file mode 100644
index 000000000..2ae6547dd
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/Google3DMap.js
@@ -0,0 +1,60 @@
+import React, { useEffect, useRef, useState } from 'react';
+import OLCesium from 'olcs/OLCesium';
+import olView from 'ol/View.js';
+import olSourceImageWMS from 'ol/source/ImageWMS.js';
+import olSourceOSM from 'ol/source/OSM.js';
+import olLayerImage from 'ol/layer/Image.js';
+import olLayerTile from 'ol/layer/Tile.js';
+import olMap from 'ol/Map.js';
+import { GLOBAL } from '../../js/consts/global';
+
+const Google3DMap = () => {
+ const mapRef = useRef(null);
+ const [ol3d, setOl3d] = useState(null);
+
+ useEffect(() => {
+ if (!mapRef.current) return;
+
+ const customProjSource = new olSourceImageWMS({
+ attributions: '© National parks / geo.admin.ch',
+ crossOrigin: 'anonymous',
+ params: { LAYERS: 'ch.bafu.schutzgebiete-paerke_nationaler_bedeutung' },
+ projection: 'EPSG:21781',
+ url: 'https://wms.geo.admin.ch/',
+ });
+
+ const { Cesium } = window;
+ Cesium.Ion.defaultAccessToken = GLOBAL.CESIUM_ION_TOKEN;
+
+ const ol2d = new olMap({
+ layers: [new olLayerTile({ source: new olSourceOSM() }), new olLayerImage({ source: customProjSource })],
+ target: mapRef.current,
+ view: new olView({
+ center: [860434.6266531206, 6029479.0044273855],
+ zoom: 6,
+ }),
+ });
+
+ const ol3dInstance = new OLCesium({
+ map: ol2d,
+ time: () => Cesium.JulianDate.now(),
+ });
+
+ const scene = ol3dInstance.getCesiumScene();
+ Cesium.createWorldTerrainAsync().then(tp => {
+ scene.terrainProvider = tp;
+ });
+
+ ol3dInstance.setEnabled(true);
+ setOl3d(ol3dInstance);
+
+ return () => {
+ ol3dInstance.setEnabled(false);
+ ol2d.setTarget(null);
+ };
+ }, []);
+
+ return ;
+};
+
+export default Google3DMap;
diff --git a/packages/common/business_components/src/ui/map/MapToggleBtn.js b/packages/common/business_components/src/ui/map/MapToggleBtn.js
new file mode 100644
index 000000000..fe8258ecb
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/MapToggleBtn.js
@@ -0,0 +1,32 @@
+import React, { useContext, useState } from 'react';
+import { Button } from "@common/components";
+import { GisContext } from './Gis';
+import GisApp from './lib/gis.core';
+
+export default React.memo(function MapToggleBtn() {
+ const { gisCore } = useContext(GisContext);
+ const [activeMap, setActiveMap] = useState({ label: '일반', type: 'base' }); // 기본 선택값
+
+ const handleSelect = mapObj => {
+ setActiveMap(mapObj);
+ gisCore.changeBaseLayer(mapObj.type);
+ };
+
+ return (
+
+
+
+ -
+
+
+ -
+
+
+
+
+ );
+});
diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.common.css b/packages/common/business_components/src/ui/map/lib/css/gis.common.css
new file mode 100644
index 000000000..de98d8a9b
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/css/gis.common.css
@@ -0,0 +1,1037 @@
+@charset "UTF-8";
+
+.custom-toolbar {
+ position: absolute;
+ top: -20px;
+ right: 0px;
+}
+.mapbtn {
+ position: absolute;
+ top: 10px;
+ right: 20px;
+}
+.mapbtn h3 {
+ overflow: hidden;
+ font-size: 0;
+ line-height: 0;
+ text-indent: -9999px;
+}
+.mapbtn > button {
+ overflow: hidden;
+ width: 40px;
+ height: 38px;
+ background: url(../images/toolbar/ico_gis_sprite.png) #fff no-repeat;
+ border: 1px solid #9aa7b8;
+ border-radius: 3px;
+ box-shadow: 0px 0px 5px #999;
+}
+.mapbtn .map1 {
+ background-image: url('../images/toolbar/img_map1.png');
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ width: 60px;
+ height: 40px;
+}
+.mapbtn .map2 {
+ background-image: url('../images/toolbar/img_map2.png');
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ width: 60px;
+ height: 40px;
+}
+.mapbtn > button > div {
+ display: none;
+ overflow: hidden;
+ text-indent: -9999px;
+}
+.mapbtn .viewer {
+ background-position: 0 -120px;
+}
+.mapbtn > a.danmyun {
+ background: url(../images/toolbar/ico_gis_sprite.png) #fff 0 -400px;
+}
+.mapbtn > a:hover {
+ margin: -1px;
+ border: 2px solid #1357ce;
+}
+.mapbtn .choice_map {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 20px;
+ width: 150px;
+}
+.mapbtn:hover ul.choice_map {
+ display: block;
+}
+.mapbtn ul.choice_map {
+ font-size: 0;
+}
+.mapbtn ul.choice_map li {
+ float: left;
+ margin-right: 5px;
+ background: #fff;
+ width: 60px;
+ text-align: center;
+ border: 2px solid transparent;
+ border-radius: 3px;
+ box-shadow: 0px 0px 5px #999;
+ overflow: hidden;
+}
+.mapbtn ul.choice_map li:last-child {
+ margin-right: 0;
+}
+.mapbtn ul.choice_map > li:hover {
+ border: 2px solid #1357ce;
+}
+.mapbtn ul.choice_map > li.on {
+ border: 2px solid #1357ce;
+}
+.mapbtn ul.choice_map li span {
+ font-size: 12px;
+ color: #999;
+ text-align: center;
+}
+.mapbtn ul.choice_map li.on span {
+ font-size: 12px;
+ color: #333;
+ text-align: center;
+}
+
+.tool-bar {
+ position: absolute;
+ top: 140px;
+ right: 20px;
+ z-index: 2;
+}
+.tool-bar h3 {
+ overflow: hidden;
+ font-size: 0;
+ line-height: 0;
+ text-indent: -9999px;
+}
+.tool-bar .status {
+ margin-bottom: 10px;
+ overflow: hidden;
+ width: 40px;
+ height: 40px;
+ background: #fff;
+ border: 1px solid #9aa7b8;
+ border-radius: 3px;
+ box-shadow: 0px 0px 5px #999;
+ box-sizing: border-box;
+}
+.tool-bar .status div {
+ display: block;
+ width: 40px;
+ height: 40px;
+ background: url(../images/toolbar/ico_gis_sprite.png) no-repeat;
+ background-position: 0 -160px;
+ text-indent: -9999px;
+}
+.tool-bar .status div:hover {
+ background-position: -40px -160px;
+}
+.tool-bar .status div.on {
+ background: url(../images/toolbar/ico_gis_sprite.png) no-repeat;
+ background-position: 0 -200px;
+}
+.tool-bar .status div.on:hover {
+ background-position: -40px -200px;
+}
+.tool-bar ul {
+ display: inline-block;
+ overflow: hidden;
+ width: 40px;
+ background: #fff;
+ border: 1px solid #9aa7b8;
+ border-radius: 3px;
+ box-shadow: 0px 0px 5px #999;
+ font-size: 0;
+ box-sizing: border-box;
+ padding: 0px;
+}
+.tool-bar ul li {
+ height: 40px;
+ border-bottom: 1px solid #eeeeee;
+ box-sizing: border-box;
+}
+.tool-bar ul li:last-child {
+ border-bottom: 0 none;
+}
+.tool-bar ul li div {
+ width: 40px;
+ height: 40px;
+ display: inline-block;
+}
+.tool-bar ul li div span {
+ display: none;
+ overflow: hidden;
+ text-indent: -9999px;
+}
+
+.ol-scale-line {
+ background: rgba(0, 60, 136, 0.3);
+ border-radius: 4px;
+ padding: 2px;
+ position: absolute;
+ bottom: 10px;
+ right: 200px;
+}
+
+/* 로드뷰 관련 */
+#roadview-container {
+ width: 100%;
+ height: 100%;
+ z-index: 500;
+ display: block;
+ position: unset;
+ left: 50%;
+ top: 0px;
+}
+#roadview-container .roadview-container-1p {
+ width: 50%;
+ height: 100%;
+ float: left;
+ position: relative;
+}
+#roadview-container .roadview-container-2p {
+ width: 50%;
+ height: 100%;
+ float: left;
+ position: relative;
+}
+#roadview-container .roadview {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+ z-index: 0;
+ background: #999;
+}
+
+/* Map Contents: Map Control Bar */
+.map_control {
+ position: absolute;
+ right: 20px;
+ top: 200px;
+ display: block;
+}
+.control_icons {
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 4px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+ padding: 10px;
+ display: block;
+}
+.control_icons li a {
+ display: block;
+ width: 40px;
+ height: 40px;
+ font-size: 0;
+}
+.control_icons li a:hover {
+ border-radius: 100%;
+ box-shadow: 0 3px 5px rgba(0, 0, 0, 0.1);
+}
+.btn_control_zoomin {
+ background: url(../images/toolbar/btn_cont_zoomin.png) no-repeat center;
+}
+.btn_control_zoomin:hover {
+ background: url(../images/toolbar/btn_cont_zoomin_on.png) no-repeat center #67aee3;
+}
+.btn_control_zoomout {
+ background: url(../images/toolbar/btn_cont_zoomout.png) no-repeat center !important;
+}
+.btn_control_zoomout:hover {
+ background: url(../images/toolbar/btn_cont_zoomout_on.png) no-repeat center #67aee3;
+}
+.btn_control_prev {
+ background: url(../images/toolbar/btn_cont_prev.png) no-repeat center;
+}
+.btn_control_prev:hover {
+ background: url(../images/toolbar/btn_cont_prev_on.png) no-repeat center #67aee3;
+}
+.btn_control_next {
+ background: url(../images/toolbar/btn_cont_next.png) no-repeat center;
+}
+.btn_control_next:hover {
+ background: url(../images/toolbar/btn_cont_next_on.png) no-repeat center #67aee3;
+}
+.btn_control_ruler {
+ background: url(../images/toolbar/btn_cont_ruler.png) no-repeat center center;
+}
+.btn_control_ruler:hover {
+ background: url(../images/toolbar/btn_cont_ruler_on.png) no-repeat center #67aee3;
+}
+.btn_control_ruler.active {
+ background: url(../images/toolbar/btn_cont_ruler_on.png) no-repeat center #67aee3;
+}
+.btn_control_area {
+ background: url(../images/toolbar/btn_cont_area.png) no-repeat center;
+}
+.btn_control_area:hover {
+ background: url(../images/toolbar/btn_cont_area_on.png) no-repeat center #67aee3;
+}
+.btn_control_area.active {
+ background: url(../images/toolbar/btn_cont_area_on.png) no-repeat center #67aee3;
+}
+.btn_control_save {
+ background: url(../images/toolbar/btn_cont_save.png) no-repeat center;
+}
+.btn_control_save:hover {
+ background: url(../images/toolbar/btn_cont_save_on.png) no-repeat center #67aee3;
+}
+.btn_control_sketch {
+ background: url(../images/toolbar/btn_cont_sketch.png) no-repeat center;
+}
+.btn_control_sketch:hover {
+ background: url(../images/toolbar/btn_cont_sketch_on.png) no-repeat center #67aee3;
+}
+.btn_control_print {
+ background: url(../images/toolbar/btn_cont_print.png) no-repeat center;
+}
+.btn_control_print:hover {
+ background: url(../images/toolbar/btn_cont_print_on.png) no-repeat center #67aee3;
+}
+
+/*하단 */
+.map-scale-area {
+ position: absolute;
+ bottom: 40px;
+ right: 200px;
+ display: inline-block;
+ z-index: 101;
+ list-style: none;
+}
+.map-scale-area li {
+ float: left;
+ margin-right: 15px;
+ font-size: 12px;
+ font-weight: 600;
+ color: #333;
+ text-shadow: -2px 0 #fff, 0 2px #fff, 2px 0 #fff, 0 -2px #fff;
+ -moz-text-shadow: -2px 0 #fff, 0 2px #fff, 2px 0 #fff, 0 -2px #fff;
+ -webkit-text-shadow: -2px 0 #fff, 0 2px #fff, 2px 0 #fff, 0 -2px #fff;
+}
+.map-scale-area li:last-child {
+ margin-right: 0;
+}
+.map-scale-area .scale-bar {
+ margin-right: 12px;
+ margin-top: 5px;
+ padding: 0 10px;
+ border: 2px solid #333;
+ border-top: 0 none;
+ height: 9px;
+ position: relative;
+ z-index: 999;
+}
+.map-scale-area .scale-bar .line-text {
+ position: relative;
+ top: -11px;
+}
+
+/* sprite */
+.sprite_gis {
+ display: inline-block; /* overflow: hidden; */
+ background: url(../images/toolbar/ico_gis_sprite.png) 0 0 no-repeat;
+ line-height: 999px;
+ vertical-align: bottom;
+}
+
+.select_control {
+ position: absolute;
+ top: 80px;
+ left: 250px;
+}
+.select_control h3 {
+ overflow: hidden;
+ font-size: 0;
+ line-height: 0;
+ text-indent: -9999px;
+}
+.select_control ul {
+ display: inline-block;
+ overflow: hidden;
+ height: 40px;
+ font-size: 0;
+ background: #fff;
+ border: 1px solid #9aa7b8;
+ border-radius: 3px;
+ box-shadow: 0px 0px 5px #999;
+ box-sizing: border-box;
+ padding: 0px;
+ margin: 0px;
+}
+.select_control ul li {
+ float: left;
+ width: 40px;
+ border-right: 1px solid #eeeeee;
+}
+.select_control ul li:last-child {
+ border-right: 0 none;
+}
+.select_control ul li div {
+ overflow: hidden;
+ width: 40px;
+ height: 40px;
+ background: url(../images/toolbar/ico_gis_sprite.png) no-repeat;
+}
+.select_control ul li div span {
+ display: none;
+ overflow: hidden;
+ text-indent: -9999px;
+}
+.select_control .sprite_gis.icon1 {
+ background-position: 0 0;
+}
+.select_control .sprite_gis.icon1:hover {
+ background-position: -40px 0;
+}
+.select_control .sprite_gis.icon2 {
+ background-position: 0 -40px;
+}
+.select_control .sprite_gis.icon2:hover {
+ background-position: -40px -40px;
+}
+.select_control .sprite_gis.icon3 {
+ background-position: 0 -80px;
+}
+.select_control .sprite_gis.icon3:hover {
+ background-position: -40px -80px;
+}
+.select_control .sprite_gis.icon4 {
+ background-position: 0 -360px;
+}
+.select_control .sprite_gis.icon4:hover {
+ background-position: -40px -360px;
+}
+.select_control .sprite_gis.icon4.active {
+ background-position: -40px -360px;
+}
+
+/* 20230516 하수도 자산관리용 아이콘*/
+.sprite_gis.icon4 {
+ background-position: 0 -360px;
+}
+.sprite_gis.icon4:hover {
+ background-position: -40px -360px !important;
+ background-color: #67aee3;
+}
+
+.select_control select {
+ float: right;
+ min-width: 100px;
+ height: 40px;
+ line-height: 40px;
+ margin-left: 10px;
+ padding-left: 10px;
+ font-size: 13px;
+ color: #666;
+ border: 1px solid #fff;
+ border-radius: 3px;
+ box-shadow: 0px 0px 5px #999;
+}
+
+.select_control-left0 {
+ left: 0px;
+}
+
+/*** result_conts ***/
+.result_conts {
+ position: absolute;
+ top: 92px;
+ left: -320px;
+ height: 540px;
+ bottom: 0;
+ width: 320px;
+ border-right: 1px solid #bdbdbd;
+ box-shadow: 0px 0px 10px #999;
+ background: #fff;
+ box-sizing: border-box;
+ z-index: 500;
+ -webkit-transition: left 0.2s ease-out, opacity 0.3s ease-out;
+ -moz-transition: left 0.2s ease-out, opacity 0.3s ease-out;
+ transition: left 0.2s ease-out, opacity 0.3s ease-out;
+}
+.result_conts button.toggle {
+ display: block;
+ width: 30px;
+ height: 50px;
+ position: absolute;
+ top: 50%;
+ right: -30px;
+ margin-top: -28px;
+ background: url(../images/toolbar/toggle_off.png);
+ outline: none;
+ border: none;
+}
+.result_conts.on button.toggle {
+ background: url(../images/toolbar/toggle_on.png);
+}
+.result_conts.on {
+ left: 0;
+}
+
+.result_conts .cheader {
+ padding: 16px 20px;
+ border-bottom: 1px solid #dddddd;
+ font-size: 14px;
+ line-height: 18px;
+ box-sizing: border-box;
+}
+.result_conts .cheader span.title {
+ color: #666;
+}
+.result_conts .cheader span.title em {
+ padding-right: 5px;
+ color: #152346;
+ font-weight: bold;
+}
+.result_conts .cbody {
+ position: absolute;
+ top: 51px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 15px 10px 0;
+ overflow: hidden;
+ overflow-y: auto;
+}
+.result_conts .cbody ul.list {
+ position: absolute;
+ top: 54px;
+ bottom: 41px;
+ left: 10px;
+ right: 10px;
+ overflow: hidden;
+ overflow-y: auto;
+ color: #999;
+ list-style: none;
+ padding: 0px;
+}
+.result_conts .cbody ul.list li {
+ border-bottom: 1px dotted #bbbbbb;
+}
+.result_conts .cbody .list li a {
+ color: rgb(98, 89, 202);
+ display: block;
+ width: 100%;
+ padding: 19px 0 15px 10px;
+}
+.result_conts .cbody .list li a .zip {
+ display: inline-block;
+ padding: 0 5px;
+ border: 1px solid #152346;
+ font-size: 12px;
+ color: #152346;
+ margin-right: 10px;
+}
+.result_conts .cbody .list li a .zip2 {
+ display: inline-block;
+ padding: 0 5px;
+ border: 1px solid #cc33cc;
+ font-size: 12px;
+ color: #cc33cc;
+}
+.result_conts .cbody .list li a .addr {
+ font-size: 0.875rem;
+ display: block;
+ padding-top: 5px;
+ color: #989898;
+}
+.t_count {
+ font-size: 0.875rem;
+}
+
+/** 시설물정보 검색결과 하수관용도별 색상구분 : 하수관로 **/
+.result_conts .cbody .list li a .addr .SBA000_facility_init {
+ color: #999999;
+}
+.result_conts .cbody .list li a .addr .SBA001_facility_init {
+ color: #cc9933;
+}
+.result_conts .cbody .list li a .addr .SBA002_facility_init {
+ color: #ff9900;
+}
+.result_conts .cbody .list li a .addr .SBA003_facility_init {
+ color: #0000ff;
+}
+.result_conts .cbody .list li a .addr .SBA004_facility_init {
+ color: #cc0000;
+}
+.result_conts .cbody .list li a .addr .SBA999_facility_init {
+ color: #222222;
+}
+
+/** 시설물정보 검색결과 하수관용도별 색상구분 : 하수맨홀 **/
+.result_conts .cbody .list li a .addr .SMU000_facility_init {
+ color: #999999;
+}
+.result_conts .cbody .list li a .addr .SMU001_facility_init {
+ color: #0000ff;
+}
+.result_conts .cbody .list li a .addr .SMU002_facility_init {
+ color: #cc0000;
+}
+.result_conts .cbody .list li a .addr .SMU003_facility_init {
+ color: #ff9900;
+}
+.result_conts .cbody .list li a .addr .SMU004_facility_init {
+ color: #cc9933;
+}
+.result_conts .cbody .list li a .addr .SMU999_facility_init {
+ color: #222222;
+}
+
+/** 시설물정보 검색결과 하수관용도별 색상구분 : 물받이 **/
+.result_conts .cbody .list li a .addr .SBD000_facility_init {
+ color: #999999;
+}
+.result_conts .cbody .list li a .addr .SBD001_facility_init {
+ color: #0000ff;
+}
+.result_conts .cbody .list li a .addr .SBD002_facility_init {
+ color: #cc0000;
+}
+.result_conts .cbody .list li a .addr .SBD003_facility_init {
+ color: #ff9900;
+}
+.result_conts .cbody .list li a .addr .SBD999_facility_init {
+ color: #222222;
+}
+
+.result_conts .cbody .list li.on,
+.result_conts .cbody .list li:hover {
+ background-color: #ebebeb;
+}
+.result_conts .cbody .list li.on a .zip,
+.result_conts .cbody .list li:hover a .zip {
+ color: #fff;
+ background-color: #152346;
+}
+.result_conts .cbody .list li.on a .addr,
+.result_conts .cbody .list li:hover a .addr {
+ color: #000;
+}
+
+.result_conts .cbody .paginate {
+ position: absolute;
+ bottom: 0;
+ left: 20px;
+ right: 0;
+ padding: 10px 0;
+ font-size: 11px;
+ line-height: normal;
+ text-align: center;
+}
+
+/*** 페이징 **/
+.paginate {
+ padding: 10px 0;
+ font-size: 11px;
+ line-height: normal;
+ text-align: center;
+}
+.result_conts .cbody .paginate .pagination .page-link {
+ font-size: 11px !important;
+}
+
+/*** 검색결과 더보기 **/
+.result_facility {
+ position: absolute;
+ left: 320px;
+ bottom: 0;
+ width: 1280px;
+ height: 200px;
+ border-radius: 10px;
+ border-right: 1px solid #bdbdbd;
+ box-shadow: 0px 0px 10px #999;
+ background: #fff;
+ box-sizing: border-box;
+ z-index: 500;
+ -webkit-transition: left 0.2s ease-out, opacity 0.3s ease-out;
+ -moz-transition: left 0.2s ease-out, opacity 0.3s ease-out;
+ transition: left 0.2s ease-out, opacity 0.3s ease-out;
+}
+
+.result_facility_popup {
+ width: 1130px;
+}
+
+.result_facility .cheader {
+ padding: 16px 20px;
+ border-bottom: 1px solid #dddddd;
+ font-size: 35px;
+ line-height: 18px;
+ box-sizing: border-box;
+ color: #666;
+ text-align: center;
+}
+
+/*** 상수도 시설현황 **/
+.fcltyLayerSmmyDiv {
+ position: absolute;
+ top: 10px;
+ left: 500px;
+ bottom: 0;
+ background: #fff;
+ width: 130px;
+ overflow-x: hidden;
+ height: 40px;
+ overflow-y: hidden;
+ border-radius: 5px;
+}
+.fcltyLayerSmmyHeaderArea {
+ background: #ffffff;
+ color: black;
+}
+.fcltyLayerSmmyHeaderArea span {
+ color: #000000;
+ font-size: 16px;
+ padding-left: 10px;
+ line-height: 40px;
+}
+
+.assetsSmmyDiv {
+ position: absolute;
+ top: 55px;
+ left: 320px;
+ bottom: 0;
+ background: #fff;
+ width: 310px;
+ overflow-x: hidden;
+ height: 690px;
+ overflow-y: auto;
+ border-radius: 5px;
+}
+.assetsNotMappingDiv {
+ position: absolute;
+ top: 10px;
+ right: 20px;
+ bottom: 0;
+ background: #fff;
+ width: 180px;
+ overflow-x: hidden;
+ height: 70px;
+ overflow-y: hidden;
+ border-radius: 5px;
+}
+.assetsNotAcceptDiv {
+ position: absolute;
+ top: 80px;
+ right: 20px;
+ bottom: 0;
+ background: #fff;
+ width: 180px;
+ overflow-x: hidden;
+ height: 70px;
+ overflow-y: hidden;
+ border-radius: 5px;
+}
+
+.puDiv {
+ position: relative;
+ width: 390px;
+ height: 180px;
+ background: #fff;
+ margin: 4px auto 10px;
+ padding: 0;
+ border-bottom: 0px;
+}
+.puDiv .puHeader {
+ display: block;
+ position: relative;
+ margin-left: 10px;
+}
+.puDiv .puContent {
+ display: block;
+ position: relative;
+ width: 395px;
+ padding: 2px;
+ text-align: center;
+ margin-left: 10px;
+}
+.puGrayTb {
+ position: relative;
+ width: 98.3%;
+ cellpadding: 0;
+ cellspcing: 0;
+ margin-top: 5px;
+ table-layout: fixed;
+ font-family: Verdana;
+}
+.puGrayTb th {
+ text-align: center;
+ height: 29px;
+ background: #b9d5ff;
+ color: #454545;
+ border: 1px solid #9d9d9d;
+ font-size: 12px;
+ font-weight: bold;
+}
+.puGrayTb td {
+ text-align: center;
+ height: 15px;
+ background: #fff;
+ color: #454545;
+ padding-left: 5px;
+ font-size: 12px;
+ border: 1px solid #cbcbcb;
+ font-weight: bold;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.puHeaderArea {
+ position: relative;
+ height: 40px;
+}
+.assetsSmmyHeaderArea {
+ background: #00c73c;
+ margin-bottom: 7px;
+}
+.assetsSmmyHeaderArea span {
+ color: #fff;
+ font-size: 16px;
+ padding-left: 10px;
+ line-height: 40px;
+}
+.puHeaderArea .close {
+ position: relative;
+ top: 5px;
+ float: right;
+ color: #fff;
+ background: url('../images/toolbar/icon-viewclose.png') no-repeat;
+ text-indent: -9999px;
+ width: 30px;
+ height: 30px;
+}
+.header h3:after {
+ content: '';
+ display: block;
+ background: #00a0ea;
+ width: 20px;
+ height: 3px;
+ font-size: 0;
+ margin-top: 5px;
+}
+
+.switch {
+ position: relative;
+ display: inline-block;
+ width: 40px;
+ height: 22px;
+ left: 10px;
+ bottom: 2px;
+}
+.switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+.slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ -webkit-transition: 0.4s;
+ transition: 0.4s;
+ border-radius: 34px;
+}
+.slider:before {
+ position: absolute;
+ content: '';
+ height: 15px;
+ width: 16px;
+ left: 4px;
+ bottom: 3.55px;
+ background-color: white;
+ -webkit-transition: 0.4s;
+ transition: 0.4s;
+ border-radius: 50%;
+}
+input:checked + .slider {
+ background-color: #7e7d7d;
+}
+input:focus + .slider {
+ box-shadow: 0 0 1px #7e7d7d;
+}
+input:checked + .slider:before {
+ -webkit-transform: translateX(26px);
+ -ms-transform: translateX(26px);
+ transform: translateX(16px);
+}
+.value {
+ position: absolute;
+ top: -11px;
+ left: -44px;
+}
+
+table.board-table th {
+ text-align: center;
+ padding: 4px 0 !important;
+ border: 1px solid #bdbdbd52;
+ color: #000000;
+ font-size: 11px;
+ width: 50px;
+ background: #ffffff;
+}
+table.board-table td {
+ text-align: center;
+ padding: 4px 0 !important;
+ border: 1px solid #f1eded;
+ color: #000000;
+ font-size: 11px;
+ width: 50px;
+ background: #ffffff;
+}
+table.board-table thead .red {
+ background: #ff0000;
+ color: #ffffff;
+ font-size: 12px;
+ text-align: center;
+ padding: 5px;
+ font-family: Verdana;
+}
+table.board-table thead .blue {
+ background: #2a63dd;
+ color: #ffffff;
+ font-size: 12px;
+ text-align: center;
+ padding: 5px;
+ font-family: Verdana;
+}
+table.board-table thead .green {
+ background: green;
+ color: #ffffff;
+ font-size: 12px;
+ text-align: center;
+ padding: 5px;
+ font-family: Verdana;
+}
+table.board-table thead .gray {
+ background: #c0c0c0;
+ color: #ffffff;
+ font-size: 12px;
+ text-align: center;
+ padding: 5px;
+ font-family: Verdana;
+}
+table.board-table tbody th td {
+ font-size: 13px;
+ font-weight: bold;
+ font-family: Verdana;
+}
+
+.ol-custom-popup {
+ position: absolute;
+ background-color: #fff;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
+ padding: 15px;
+ border-radius: 10px;
+ border: 1px solid #ccc;
+ bottom: 12px;
+ left: -50px;
+ min-width: 280px;
+ font-size: 15;
+}
+.ol-custom-popup:after,
+.ol-popup:before {
+ top: 100%;
+ border: solid transparent;
+ content: ' ';
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+}
+.ol-custom-popup:after {
+ border-top-color: #fff;
+ border-width: 10px;
+ left: 48px;
+ margin-left: -10px;
+}
+.ol-custom-popup:before {
+ border-top-color: #ccc;
+ border-width: 11px;
+ left: 48px;
+ margin-left: -11px;
+}
+.ol-custom-popup-closer {
+ text-decoration: none;
+ position: absolute;
+ top: 2px;
+ right: 8px;
+}
+.ol-custom-popup-closer:after {
+ content: '✖';
+}
+.ol-overviewmap {
+ width: 150px;
+ position: absolute;
+ z-index: 1;
+ right: 0px;
+ bottom: 0px;
+}
+.ol-overviewmap .ol-overviewmap-map {
+ border: 1px solid #7b98bc;
+ height: 100px;
+ margin: 10px;
+}
+.ol-overviewmap-box {
+ border: 2px dotted rgba(0, 60, 136, 0.7);
+}
+.ol-overviewmap button {
+ display: none;
+}
+.ol-overviewmap-map canvas {
+ height: 100%;
+}
+
+.gis-map canvas {
+ transform: matrix(1, 0, 0, 1, 0, 0) !important;
+ width: inherit;
+ height: inherit;
+}
+
+.gis-content .evl-legends{
+ bottom: -20px !important;
+ left: 1350px;
+ z-index: 101;
+}
+
+
+/* cesium-viewer-toolbar 숨기기 */
+.cesium-viewer-toolbar {
+ display: none;
+}
+
+/* cesium-viewer-animationContainer 숨기기 */
+.cesium-viewer-animationContainer {
+ display: none;
+}
+.cesium-viewer-bottom {
+ display: none;
+}
+.cesium-viewer-timelineContainer {
+ display: none;
+}
+.cesium-credit-logoContainer{
+ display: none !important;
+}
+.cesium-credit-wrapper{
+ display: none !important;
+}
+.cesium-credit-expand-link{
+ display: none !important;
+}
+
+
diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.edit.css b/packages/common/business_components/src/ui/map/lib/css/gis.edit.css
new file mode 100644
index 000000000..c2531e4b0
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/css/gis.edit.css
@@ -0,0 +1,192 @@
+@charset 'utf-8';
+
+.edit_control {
+ position: absolute;
+ top: 20px;
+ left: 610px;
+}
+.edit_control h3 {
+ overflow: hidden;
+ font-size: 0;
+ line-height: 0;
+ text-indent: -9999px;
+}
+.edit_control ul {
+ float: left;
+ overflow: hidden;
+ height: 40px;
+ margin-left: 10px;
+ background: #fff;
+ font-size: 0;
+ border: 1px solid #9aa7b8;
+ border-radius: 3px;
+ box-shadow: 0px 0px 5px #999;
+ box-sizing: border-box;
+ padding: 0;
+ cursor: pointer;
+}
+.edit_control ul li {
+ display: inline-block;
+ width: 40px;
+ height: 40px;
+ border-right: 1px solid #eeeeee;
+}
+.edit_control ul li:last-child {
+ border-right: 0 none;
+}
+/*background 삭제*/
+/* .edit_control ul li a {overflow:hidden;width: 40px;height: 40px;background: url(../../images/om/ico_gis_sprite.png) no-repeat;} */
+.edit_control ul li a {
+ overflow: hidden;
+ width: 40px;
+ height: 40px;
+ display: inline-block;
+ padding: 0;
+}
+.edit_control ul li a span {
+ display: none;
+ overflow: hidden;
+ text-indent: -9999px;
+}
+.edit_control select {
+ float: left;
+ min-width: 150px;
+ height: 40px;
+ line-height: 40px;
+ margin-left: 10px;
+ padding-left: 10px;
+ font-size: 13px;
+ color: #666;
+ border: 1px solid #fff;
+ border-radius: 3px;
+ box-shadow: 0px 0px 5px #999;
+}
+
+.edit_control input {
+ float: left;
+ min-width: 150px;
+ height: 40px;
+ line-height: 40px;
+ margin-left: 10px;
+ padding-left: 10px;
+ font-size: 13px;
+ color: #666;
+ border: 1px solid #fff;
+ border-radius: 3px;
+ box-shadow: 0px 0px 5px #999;
+}
+
+.edit_control button {
+ float: left;
+ min-width: 80px;
+ height: 40px;
+ margin-left: 5px;
+ font-size: 13px;
+ color: #fff;
+ text-align: center;
+ border-radius: 3px;
+ cursor: pointer;
+}
+.edit_control button:hover {
+ background: #1357ce;
+}
+
+.edit_toolbar {
+ position: absolute;
+ left: 280px;
+ top: 52px;
+}
+.btn_edit_slt {
+ background: url(../images/toolbar/btn_edit_slt.png) no-repeat center;
+ height: 40px;
+}
+.btn_edit_slt:hover {
+ background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center;
+ height: 40px;
+}
+.btn_edit_slt.active {
+ background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center;
+ height: 40px;
+}
+.btn_edit_add {
+ background: url(../images/toolbar/btn_edit_add.png) no-repeat center;
+ height: 40px;
+}
+.btn_edit_add:hover {
+ background: url(../images/toolbar/btn_edit_add_on.png) no-repeat center;
+ height: 40px;
+}
+.btn_edit_add.active {
+ background: url(../images/toolbar/btn_edit_add_on.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_del {
+ background: url(../images/toolbar/btn_edit_del.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_del:hover {
+ background: url(../images/toolbar/btn_edit_del_on.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_prev {
+ background: url(../images/toolbar/btn_edit_prev.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_prev:hover {
+ background: url(../images/toolbar/btn_edit_prev_on.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_next {
+ background: url(../images/toolbar/btn_edit_next.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_next:hover {
+ background: url(../images/toolbar/btn_edit_next_on.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_snap {
+ background: url(../images/toolbar/btn_edit_snap.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_snap:hover {
+ background: url(../images/toolbar/btn_edit_snap_on.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_snap.active {
+ background: url(../images/toolbar/btn_edit_snap_on.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_save {
+ background: url(../images/toolbar/btn_edit_save.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_save:hover {
+ background: url(../images/toolbar/btn_edit_save_on.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_transform {
+ background: url(../images/toolbar/btn_edit_slt.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_transform:hover {
+ background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center;
+ height: 40px;
+}
+
+.btn_edit_transform.active {
+ background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center;
+ height: 40px;
+}
diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.roadview.css b/packages/common/business_components/src/ui/map/lib/css/gis.roadview.css
new file mode 100644
index 000000000..917d1b778
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/css/gis.roadview.css
@@ -0,0 +1,7 @@
+@charset 'utf-8';
+/* 로드뷰 관련 */
+.roadview-container {width: 100%; height: 100%; z-index:500; display:block;position: absolute; left: 0px;top: 0px;}
+.roadview-container .roadview-container-1p {width: 310px; height: 190px; bottom: 15px; left: 15px; top: auto; border-radius: 2px; overflow: hidden; z-index: 1000; position: absolute;}
+.roadview-container .roadview-container-2p {width: 100%; height:100%; float:left; position:relative;}
+.roadview-container .roadview {position:absolute; top:0; right:0; left:0; bottom:0; z-index:0; background:#999;}
+
diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.sketch.css b/packages/common/business_components/src/ui/map/lib/css/gis.sketch.css
new file mode 100644
index 000000000..e69c02a7a
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/css/gis.sketch.css
@@ -0,0 +1,206 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol,
+ul {
+ list-style: none;
+}
+blockquote,
+q {
+ quotes: none;
+}
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+ content: "";
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+body {
+ background-color: #e6e6fc;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 50px 0px;
+}
+
+.canvas {
+ width: 800px;
+ height: 500px;
+ background-color: white;
+ border-radius: 15px;
+ box-shadow: 6px 6px 5px grey;
+}
+
+.controls .controls__colors {
+ display: flex;
+}
+.controls__colors .controls__color {
+ width: 50px;
+ height: 50px;
+ border-radius: 25px;
+ margin-right: 5px;
+ cursor: pointer;
+ box-shadow: 3px 3px 5px grey;
+}
+
+.jscolor {
+ color: transparent;
+ font-size: 0;
+ line-height: 0;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 25px;
+ border-width: 0px;
+ margin-right: 5px;
+ cursor: pointer;
+ box-shadow: 3px 3px 5px grey;
+}
+
+.controls {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.controls .controls__btns {
+ margin-top: 20px;
+ margin-bottom: 30px;
+}
+
+.controls__btns button:active {
+ transform: scale(0.98);
+}
+
+.controls__btns button {
+ all: unset;
+ cursor: pointer;
+ background-color: white;
+ padding: 5px 0px;
+ width: 80px;
+ text-align: center;
+ border-radius: 10px;
+ box-shadow: 3px 3px 3px grey;
+ border: 2px solid rgba(0, 0, 0, 0.2);
+ color: rgba(0, 0, 0, 0.8);
+ text-transform: uppercase;
+ font-weight: 600;
+ font-size: 12px;
+}
+
+.controls .controls__range {
+ margin-top: 20px;
+}
diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.tooltip.css b/packages/common/business_components/src/ui/map/lib/css/gis.tooltip.css
new file mode 100644
index 000000000..043d842e2
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/css/gis.tooltip.css
@@ -0,0 +1,35 @@
+@charset 'utf-8';
+
+.ol-tooltip {
+ position: relative;
+ background: rgba(0, 0, 0, 0.5);
+ border-radius: 4px;
+ color: white;
+ padding: 4px 8px;
+ opacity: 0.7;
+ white-space: nowrap;
+ font-size: 12px;
+}
+.ol-tooltip-measure {
+ opacity: 1;
+ font-weight: bold;
+}
+.ol-tooltip-static {
+ background-color: #ffcc33;
+ color: black;
+ border: 1px solid white;
+}
+.ol-tooltip-measure:before,
+.ol-tooltip-static:before {
+ border-top: 6px solid rgba(0, 0, 0, 0.5);
+ border-right: 6px solid transparent;
+ border-left: 6px solid transparent;
+ content: "";
+ position: absolute;
+ bottom: -6px;
+ margin-left: -7px;
+ left: 50%;
+}
+.ol-tooltip-static:before {
+ border-top-color: #ffcc33;
+}
\ No newline at end of file
diff --git a/packages/common/business_components/src/ui/map/lib/gis.core.js b/packages/common/business_components/src/ui/map/lib/gis.core.js
new file mode 100644
index 000000000..faebb90d3
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/gis.core.js
@@ -0,0 +1,923 @@
+import MousePosition from 'ol/control/MousePosition';
+import ScaleLine from 'ol/control/ScaleLine';
+import Feature from 'ol/Feature';
+import WKT from 'ol/format/WKT';
+import LinearRing from 'ol/geom/LinearRing';
+import Draw from 'ol/interaction/Draw';
+import TileLayer from 'ol/layer/Tile';
+import * as olProj from 'ol/proj';
+import VectorSource from 'ol/source/Vector';
+import XYZ from 'ol/source/XYZ';
+import GeoSJON from 'ol/format/GeoJSON';
+import { Fill, Stroke, Style } from 'ol/style';
+import CircleStyle from 'ol/style/Circle';
+import VectorLayer from 'ol/layer/Vector';
+import { Point } from 'ol/geom';
+import { boundingExtent } from 'ol/extent';
+import olSourceImageWMS from 'ol/source/ImageWMS';
+import { GeoJSON } from 'ol/format';
+import GisOlEditLayer from './gis.edit';
+import GisOlLayer from './gis.layer';
+
+const GisApp = {};
+/**
+ * GisApp 전역변수 생성
+ */
+//레이어 정보
+GisApp.layerInfos = [];
+//기본정보
+//GisApp.GEOSERVER_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/ows';
+//GisApp.GEOSERVER_WMS_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/wms';
+GisApp.GEOSERVER_URL = '/sn/geoserver/hasudo/ows';
+GisApp.GEOSERVER_WMS_URL = '/sn/geoserver/hasudo/wms';
+
+//맵 정보
+GisApp.EMAP_URL = '/emap/';
+GisApp.EMAP_SATELLITE_URL = '/emap/satellite/';
+GisApp.VWORLD_URL = 'https://xdworld.vworld.kr/2d/Base/service/{z}/{x}/{y}.png';
+GisApp.VWORLD_SATELLITE_URL = 'https://xdworld.vworld.kr/2d/Satellite/service/{z}/{x}/{y}.jpeg';
+GisApp.GOOGLE_API_KEY = 'AIzaSyCf1sgeELFalfPuqlykRKBMj6X9u_DQH6M';
+GisApp.GOOGLE_ROADMAP_URL = `https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`;
+GisApp.GOOGLE_SATELLITE_URL = `https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`;
+GisApp.GOOGLE_HYBRID_URL = `https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`;
+GisApp.GOOGLE_3D_URL = `https://tile.googleapis.com/v1/3dtiles/root.json?key=${GisApp.GOOGLE_API_KEY}`;
+GisApp.OPENSTREET_URL = '//tile.openstreetmap.org/{z}/{x}/{y}.png';
+GisApp.BASE_MAP_TYPES = [];
+
+GisApp.FROM_PROJ = 'EPSG:4326';
+GisApp.TO_PROJ = 'EPSG:3857';
+GisApp.TO_DATA_PROJ = 'EPSG:4326';
+GisApp.CONTEXT_LAYER_PATH = '/sn/images/map/layericon/';
+GisApp.layerCode = {};
+GisApp.hlLayerId = 'LightFeatureList';
+GisApp.hLayerId = 'highLightFeature';
+GisApp.wtlkgAnal = 'wtlkgAnal';
+
+GisApp.Module = {
+ core: null,
+ select: null,
+ measure: null,
+ layer: null,
+ style: null,
+ edit: null,
+};
+
+/**
+ * GisApp.layerInfos Set
+ */
+GisApp.setLayerInfos = function (layerInfos) {
+ GisApp.layerInfos = layerInfos;
+};
+
+/**
+ * GisApp core
+ * @requires gis.layer.js
+ */
+GisApp.core = function (options) {
+ GisApp.core.prototype.initialize(options);
+};
+
+GisApp.core.prototype = {
+ defaults: {
+ target: '',
+ centerX: 37.2806094,
+ centerY: 127.0032502,
+ zoom: 11,
+ minZoom: 5,
+ maxZoom: 22,
+ },
+ options: {
+ /*1: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 35.3227711, centerY: 129.1836202, dataProj: 'EPSG:5186' },
+ 4: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 35.0140499, centerY: 126.7110757, dataProj: 'EPSG:5186' },
+ 14: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 34.9390499, centerY: 127.6960757, dataProj: 'EPSG:5186' },*/
+ },
+ epsg_5179: null,
+ map: null, // ol.Map
+ view: {}, // ol.view
+ interactions: {}, // interaction 모음 객체
+ selectedFeature: null, // 현재 선택된 feature
+ baseLayers: {},
+ baseMiniMapLayers: {},
+ layers: {},
+ initialize: function (options) {
+ let option = this.options['1'];
+ if (option) {
+ Object.assign(this.defaults, option);
+ GisApp.TO_DATA_PROJ = option.dataProj;
+ }
+ Object.assign(this.defaults, options);
+ GisApp.Module.core = this;
+ GisApp.Module.layer = new GisOlLayer(this.defaults);
+ GisApp.Module.edit = new GisOlEditLayer(this.defaults);
+
+ this.baseMiniMapLayers['base'] = this.getBaseMap('minimap-base');
+ this.baseLayers['base'] = this.getBaseMap('base');
+ this.baseLayers['satellite'] = this.getBaseMap('satellite');
+ this.baseLayers['3d'] = this.getBaseMap('3d');
+ },
+ layerType: {
+ point: 'POINT',
+ line: 'LINE',
+ poligon: 'POLIGON',
+ },
+ chgMngFcltOptions: function (fcltCd) {
+ let option = this.options[fcltCd];
+ Object.assign(this.defaults, option);
+ this.setCenter(Number(this.defaults.centerX), Number(this.defaults.centerY));
+ this.setZoom(15);
+ },
+ getBaseMap: function (_id) {
+ let url = GisApp.VWORLD_URL;
+ if (_id === 'satellite') {
+ url = GisApp.VWORLD_SATELLITE_URL;
+ }
+ let baseMap = new TileLayer({
+ id: _id,
+ visible: true,
+ type: 'base',
+ source: new XYZ({
+ url: url,
+ crossOrigin: 'anonymous',
+ }),
+ zIndex: 0,
+ });
+ return baseMap;
+ },
+ /**
+ * [사용]테이블명으로 레이어 아이디 조회
+ */
+ getLayerCodeByTbNm: function (_tableNm) {
+ let returnLayerCode = null;
+ for (let i in GisApp.layerCode) {
+ let layerCode = GisApp.layerCode[i];
+ if (layerCode.tableName === _tableNm) {
+ returnLayerCode = layerCode;
+ }
+ }
+ return returnLayerCode;
+ },
+ /**
+ * [사용]베이스 레이어 변경
+ * Parameters:
+ * _title - {String} 베이스 레이어 이름
+ *
+ * Returns:
+ */
+ changeBaseLayer: function (_id) {
+ this.map.getLayers().forEach(function (layer) {
+ let type = layer.get('type');
+ let id = layer.get('id');
+ if (type == 'base') {
+ if (id == _id) {
+ layer.setVisible(true);
+ } else {
+ layer.setVisible(false);
+ }
+ }
+ });
+ },
+ /**
+ * [사용]지도 중심좌표 설정
+ */
+ setCenter: function (x, y, transYn) {
+ if (!x || !y) return;
+ let view = this.map.getView();
+
+ if (!transYn) {
+ // 값이 없으면
+ let center = olProj.transform([Number(y), Number(x)], GisApp.FROM_PROJ, GisApp.TO_PROJ);
+ const style = new Style({
+ image: new CircleStyle({
+ radius: 5,
+ fill: new Fill({
+ color: 'rgba(255,0,0,1)',
+ }),
+ stroke: new Stroke({
+ color: 'rgba(255,0,0,1)',
+ width: 2,
+ }),
+ }),
+ });
+
+ // 빨간색 점 추가 (스타일 적용)
+ let redPoint = new Feature({
+ geometry: new Point(center),
+ });
+
+ let redPointLayer = new VectorLayer({
+ source: new VectorSource({
+ features: [redPoint],
+ }),
+ style: style,
+ });
+ redPointLayer.set('id', 'redPointLayer');
+ // 이전에 만들었던 빨간색 점 레이어 삭제
+ this.deleteLayer('redPointLayer');
+
+ // 새로운 빨간색 점 레이어를 맵에 추가
+ this.map.addLayer(redPointLayer);
+ view.setCenter(center);
+ } else {
+ view.setCenter([Number(x), Number(y)]);
+ }
+ },
+
+ /**
+ * [사용]주소검색 결과 지도이동
+ */
+ setAddressResult: function (x, y, geom, addressName) {
+ if (!x || !y) return;
+ let view = this.map.getView();
+ let center = olProj.transform([Number(y), Number(x)], GisApp.FROM_PROJ, GisApp.TO_PROJ);
+
+ const style = [
+ new Style({
+ image: new CircleStyle({
+ radius: 5,
+ fill: new Fill({
+ color: 'rgba(255,0,0,1)',
+ }),
+ stroke: new Stroke({
+ color: 'rgba(255,0,0,1)',
+ width: 2,
+ }),
+ }),
+ }),
+ new Style({
+ stroke: new Stroke({
+ color: 'blue',
+ width: 3,
+ }),
+ fill: new Fill({
+ color: 'rgba(0, 0, 255, 0.1)',
+ }),
+ }),
+ ];
+
+ // 빨간색 점 추가 (스타일 적용)
+ let redPoint = new Feature({
+ geometry: new Point(center),
+ });
+ let redArea = new WKT().readFeature(geom, {
+ dataProjection: GisApp.FROM_PROJ,
+ featureProjection: GisApp.TO_PROJ,
+ });
+
+ let redPointLayer = new VectorLayer({
+ source: new VectorSource({
+ features: [redPoint, redArea],
+ }),
+ style: style,
+ });
+
+ redPointLayer.set('id', 'redPointLayer');
+ // 이전에 만들었던 빨간색 점 레이어 삭제
+ this.deleteLayer('redPointLayer');
+
+ // 새로운 빨간색 점 레이어를 맵에 추가
+ this.map.addLayer(redPointLayer);
+
+ view.animate({ center: center, zoom: 19 });
+ return redPointLayer;
+ },
+ /*
+ * [사용]zoom 설정
+ */
+ setZoom: function (zoom) {
+ if (!zoom) return;
+ this.map.getView().setZoom(zoom);
+ },
+ /*
+ * [사용]zoom 정보 반환
+ */
+ getZoom: function () {
+ return this.map.getView().getZoom();
+ },
+ /**
+ * [사용]레이어 맵에 추가(외부에서 생성된 layer를 파라미터로 받아 맵에 추가)
+ */
+ setWfsLayerOnMap: function (layer) {
+ this.deleteLayer(layer.get('id'));
+ this.map.addLayer(layer);
+ return layer;
+ },
+ /**
+ * [사용]레이어 생성(지오서버에서 WFS 방식으로 레이어정보 가져와 레이어 생성)
+ */
+ getWfsLayer: function (layerCodeObj) {
+ return GisApp.Module.layer.getWfsLayer(layerCodeObj);
+ },
+ /**
+ * [사용]레이어 맵에 추가(getWfsLayer 사용하여 레이어 생성)
+ */
+ addWfsLayer: function (layerCodeObj) {
+ let layer = this.getWfsLayer(layerCodeObj);
+ this.deleteLayer(layer.get('id'));
+ this.map.addLayer(layer);
+ return layer;
+ },
+ /**
+ * [사용]레이어 맵에 추가(getWmsLayer 사용하여 레이어 생성)
+ */
+ addWmsLayer: function (layerCodeObj) {
+ let layer = GisApp.Module.layer.getWmsLayer(layerCodeObj);
+ this.deleteLayer(layer.get('id'));
+ this.map.addLayer(layer);
+ return layer;
+ },
+ /**
+ * [사용]레이어 삭제
+ */
+ deleteLayer: function (layerId) {
+ const map = this.map;
+ if (!layerId) return;
+ map.getLayers().forEach(function (layer) {
+ let deleteLayer = null;
+ if (layer != null && layer.get('id') != null) {
+ let vlayerId = layer.get('id');
+ if (vlayerId) {
+ if (vlayerId === layerId) {
+ deleteLayer = layer;
+ } else if (`${vlayerId}`.includes(`${layerId}`)) {
+ deleteLayer = layer;
+ }
+ if (deleteLayer) {
+ map.removeLayer(deleteLayer);
+ }
+ }
+ }
+ });
+ },
+ /**
+ * [사용]레이어 전체 삭제
+ */
+ deleteAllLayer: function () {
+ const map = this.map;
+ map.getLayers().forEach(function (layer) {
+ if (layer?.get('type') !== 'base') {
+ map.removeLayer(layer);
+ }
+ });
+ //맵에 보이지 않는 레이어 삭제
+ GisApp.Module.layer?.layers.forEach(function (layer) {
+ map.removeLayer(layer);
+ });
+ },
+ /**
+ * [사용]레이어이름으로 레이어 조회 반환
+ */
+ getLayerById: function (layerId) {
+ if (!layerId) return;
+ let layer = null;
+ this.map.getAllLayers().forEach(function (el) {
+ if (el != null && el?.get('id') != null) {
+ let vlayerId = el?.get('id');
+ if (Number(vlayerId) === Number(layerId)) {
+ layer = el;
+ }
+ }
+ });
+ if (layer != null) {
+ GisApp.Module.layer.layers.forEach(function (el) {
+ if (el != null && el?.get('id') != null) {
+ let vlayerId = el?.get('id');
+ if (Number(vlayerId) === Number(layerId)) {
+ layer = el;
+ }
+ }
+ });
+ }
+ return layer;
+ },
+ /**
+ * [사용]오버레이 전체 삭제
+ */
+ deleteAllOverlay: function () {
+ let map = this.map;
+ let overlays = map.getOverlays();
+ if (overlays != null) {
+ let overlayArray = overlays.getArray();
+ let overlaySize = overlayArray.length;
+ if (overlaySize > 0) {
+ for (let i = overlaySize - 1; i >= 0; i--) {
+ let overlay = overlayArray[i];
+ map.removeOverlay(overlay);
+ }
+ }
+ }
+ Object.values(GisApp.Module.style.overlays).forEach(overlay => {
+ if (overlay) {
+ map.removeOverlay(overlay);
+ }
+ });
+ GisApp.Module.style.overlays = {};
+ },
+ /**
+ * [사용]오버레이 아이디로 삭제
+ */
+ deleteOverlay: function (_id) {
+ let map = this.map;
+ let overlays = map.getOverlays();
+ if (overlays != null) {
+ let overlayArray = overlays.getArray();
+ let overlaySize = overlayArray.length;
+ if (overlaySize > 0) {
+ for (let i = overlaySize - 1; i >= 0; i--) {
+ let overlay = overlayArray[i];
+ let id = overlay.getId();
+ if (id === _id) {
+ map.removeOverlay(overlay);
+ delete GisApp.Module.style.overlays[_id];
+ }
+ }
+ }
+ }
+ },
+ /**
+ * [사용]맵 이벤트 삭제
+ */
+ removeMapEventFnNm: function (handlerNm, fnNm) {
+ let map = this.map;
+ for (let i in map.listeners_[handlerNm]) {
+ let listener = map.listeners_[handlerNm][i];
+ if (listener.name == fnNm) {
+ map.listeners_[handlerNm].splice(i, 1);
+ }
+ }
+ },
+ /**
+ * [사용]위경도 -> 시분초 변환
+ */
+ coordTrans: function (coord) {
+ let str = String(coord); /* String 변환 */
+ let split = str.split('.'); /* 소수점 split */
+ let hour = split[0]; /* 시간 */
+ let minute = parseFloat((coord - parseInt(split[0]).toFixed(6)) * 60).toFixed(6);
+ let min = String(minute).split('.')[0];
+ let second = parseFloat((minute - parseInt(min)) * 60).toFixed(2);
+ return hour + '도 ' + min + '분 ' + second + '초';
+ },
+ /**
+ * [사용]시분초 -> 위경도 변환
+ */
+ coordTransrate: function (degree, minute, second) {
+ let min = (Number(minute) / 60).toFixed(2);
+ let sec = (Number(second) / 3600).toFixed(4);
+ return degree + Number(min) + Number(sec);
+ },
+ /**
+ * [사용]마우스 좌표 위치
+ */
+ mousePositionControl: function () {
+ let element = document.getElementById('label_lonlat');
+ let me = this;
+ let mousePosition = new MousePosition({
+ coordinateFormat: function (coordinate) {
+ let text = '';
+ if (element) {
+ let lng = coordinate[0];
+ let lat = coordinate[1];
+ text += '경도 : ' + lng.toFixed(7) + ' ( ' + me.coordTrans(lng) + ' ) ';
+ text += '위도 : ' + lat.toFixed(7) + '( ' + me.coordTrans(lat) + ' )';
+ }
+ return text;
+ },
+ projection: GisApp.FROM_PROJ,
+ className: 'custom-mouse-position',
+ target: document.getElementById('label_lonlat'),
+ });
+ this.map.addControl(mousePosition);
+ },
+ /**
+ * [사용]스케일 표시
+ */
+ scaleControl: function () {
+ let scale = new ScaleLine({
+ minWidth: 100,
+ units: 'metric',
+ });
+ this.map.addControl(scale);
+ },
+ /**
+ * [사용]레이어 맵에 셋팅
+ *
+ * Parameters:
+ * layerList - {Array} [layerNm : GisApp.layerCode 키]
+ * visible - {Boolean} 레이어 visible 여부
+ *
+ * Returns:
+ */
+ setLayerOnMap: function (layerIds, visible, addQurryFlter) {
+ for (let i in layerIds) {
+ let id = layerIds[i];
+ let layer = this.getLayerById(id);
+ if (layer) {
+ layer.setVisible(visible);
+ } else if (visible) {
+ const layerCode = GisApp.layerCode[id];
+ const queryFlter = layerCode?.flterSql || '' + ' ' + (addQurryFlter || '');
+ layerCode['flterSql'] = queryFlter;
+ //layer = this.addWmsLayer(layerCode);
+ layer = this.addWfsLayer(layerCode);
+ }
+ }
+ if (GisApp.Module.select) {
+ this.setSelectInteraction();
+ }
+ if (GisApp.Module.edit.snap) {
+ GisApp.Module.edit.setSnap();
+ }
+ },
+ /**
+ * [사용]shp 파일 다운로드
+ */
+ getShapeLayer: function (option) {
+ GisApp.Module.layer.getShapeLayer(option);
+ },
+ /**
+ * [사용]레이어 셀렉트 가능하게 셋팅
+ */
+ setSelectInteraction: function () {
+ let layers = this.map.getLayers();
+ if (layers) {
+ for (let i in layers.array_) {
+ let layer = layers.array_[i];
+ if (layer.get('chcYn') === 'Y') {
+ GisApp.Module.select.addLayer(layer);
+ }
+ }
+ }
+ },
+ /**
+ * [사용]layerInfos(array) To layerCode(Object)
+ */
+ convertLayerInfosToLayerCode: function (array, key, parentName) {
+ let initialValue = {};
+
+ for (let i = 0; i < array.length; i++) {
+ let item = {};
+ Object.assign(item, array[i]);
+ if (parentName) {
+ item.parentName = parentName;
+ }
+ initialValue[item[key]] = item;
+
+ if (item.children && item.children.length > 0) {
+ let child = this.convertLayerInfosToLayerCode(item.children, key, item.name);
+ Object.assign(initialValue, child);
+ }
+ if (initialValue[item[key]].children) {
+ delete initialValue[item[key]].children;
+ }
+ }
+ return initialValue;
+ },
+ /**
+ * [사용]피처 좌표값 WKT포맷으로 변경
+ */
+ convertFeatureGeomToWKT: function (feature) {
+ let geometry = feature.getGeometry();
+ const wktGeom = this.convertGeomToWkt(geometry);
+ return wktGeom;
+ },
+ /**
+ * [사용]WKT포맷 Geometry로 변경
+ */
+ convertWktToGeom: function (wtkGeom) {
+ let format = new WKT();
+ let geom = format.readGeometry(wtkGeom);
+ return geom;
+ },
+ /**
+ * [사용]좌표와 properties의 키값으로 피처 검색
+ */
+ getFeatureGeomProp: function (geometry, ftrIds, ftrCde) {
+ let returnFeature;
+ this.map.getLayers().forEach(function (layer) {
+ let type = layer.get('type');
+ if (type != 'base') {
+ let source = layer.getSource();
+
+ let type = geometry.getType();
+ let coordinates = geometry.getCoordinates();
+ if (type.toUpperCase() === 'POINT') {
+ coordinates = [coordinates];
+ } else {
+ coordinates = coordinates[0];
+ }
+ let featureFtrIds;
+ let featureFtrCde;
+ for (let i in coordinates) {
+ let coordinate = coordinates[i];
+ let feature = source.getClosestFeatureToCoordinate(coordinate);
+ if (feature) {
+ let properties = feature.getProperties();
+ featureFtrIds = properties['FTR_IDS'];
+ featureFtrCde = properties['FTR_CDE'];
+ if (ftrIds == featureFtrIds && ftrCde == featureFtrCde) {
+ returnFeature = feature;
+ }
+ }
+ }
+ }
+ });
+ return returnFeature;
+ },
+ /**
+ * [사용]관리번호로 피처검색
+ */
+ getFeatureByFtrIdn: function (typeName, ftrIdn) {
+ let returnFeature;
+ this.map.getLayers().forEach(function (layer) {
+ let layerTypeName = layer.get('typeName');
+ if (layerTypeName === typeName) {
+ layer
+ .getSource()
+ .getFeatures()
+ .forEach(feature => {
+ let featureFtrIdn = feature.get('ftrIdn');
+ if (featureFtrIdn === ftrIdn) {
+ returnFeature = feature;
+ }
+ });
+ }
+ });
+ return returnFeature;
+ },
+ /**
+ * [사용]맵 이벤트 추가
+ */
+ addMapEvent: function (evtNm, id, callback) {
+ if (id) {
+ let evt = this.map.on(evtNm, callback);
+ evt.listener.id = id;
+ }
+ },
+ /**
+ * [사용]맵 이벤트 삭제
+ */
+ removeMapEvent: function (evtNm, id) {
+ if (id) {
+ let listeners = this.map.listeners_[evtNm];
+ for (let i in listeners) {
+ let listenter = listeners[i];
+ if (listenter.id == id) {
+ listeners.splice(i, 1);
+ }
+ }
+ }
+ },
+ /**
+ * [사용]마커 그리기 추가
+ */
+ drawMarker: function (layer, callback) {
+ let map = this.map;
+ let drawSource = layer.getSource();
+ let draw = new Draw({
+ type: 'Point',
+ source: drawSource,
+ condition: function (evt) {
+ return true;
+ },
+ });
+ map.addInteraction(draw);
+ draw.on('drawend', function (event) {
+ callback(event);
+ map.removeInteraction(draw);
+ });
+ },
+ /**
+ * [사용]피처 삭제
+ */
+ deleteFeature: function (layerId, featureId) {
+ if (!layerId || !featureId) return;
+ let layer = this.getLayerById(layerId);
+ if (layer != null) {
+ let feature = layer.getSource().getFeatureById(featureId);
+ if (feature != null) {
+ layer.getSource().removeFeature(feature);
+ }
+ }
+ },
+ /**
+ * [사용]레이어
+ */
+ fitExtent: function (extent, zoom) {
+ let map = this.map;
+ let mapSize = map.getSize();
+ if (!zoom) {
+ zoom = this.getZoom();
+ }
+ if (extent) {
+ //map.getView().fit(extent, { size: [mapSize[0], mapSize[1]] });
+ this.map.getView().fit(extent, {
+ maxZoom: 18, // 너무 확대되지 않도록 최대 줌 제한
+ duration: 500, // 부드러운 애니메이션 효과 (밀리초 단위)
+ });
+ }
+ this.setZoom(zoom);
+ },
+ getGeomLineOfFeature: function (feature) {
+ if (!feature) {
+ return;
+ }
+ let geometry = feature.getGeometry();
+ let type = geometry.getType();
+ if (!(type == 'LineString' || type == 'MultiLineString')) {
+ return;
+ }
+ let geomLienString = geometry;
+ if (type != 'LineString') {
+ //lienString = geometry.getLineString();
+ }
+ return geomLienString;
+ },
+ getCenterGeomLineOfFeature: function (feature) {
+ if (!feature) {
+ return;
+ }
+ let center = [];
+ let geomLienString = this.getGeomLineOfFeature(feature);
+ let coords = geomLienString.getCoordinates();
+ let coordCnt = coords.length;
+ if (coordCnt == 1) {
+ coords = coords[0];
+ }
+ //라인 한개
+ if (coordCnt == 2) {
+ return geomLienString;
+ }
+ //멀티라인
+ let centerCnt = Math.floor(coordCnt / 2);
+ let lineCoordinates = [];
+ lineCoordinates.push(coords[centerCnt - 1]);
+ lineCoordinates.push(coords[centerCnt]);
+ geomLienString = new LinearRing(lineCoordinates);
+ return geomLienString;
+ },
+ getCenterOfLineGeom: function (geom) {
+ let center = [];
+ let extent = geom.getExtent();
+ let x = extent[0] + (extent[2] - extent[0]) / 2;
+ let y = extent[1] + (extent[3] - extent[1]) / 2;
+ center.push(x);
+ center.push(y);
+ return center;
+ },
+ /**
+ * 1 -> 01 두자리수로 변환
+ */
+ fillZeroToZoomLevel: function (n, digits) {
+ let zero = '';
+ n = n.toString();
+ if (digits > n.length) {
+ for (let i = 0; digits - n.length > i; i++) {
+ zero += '0';
+ }
+ }
+ return zero + n;
+ },
+ convertGeoJsonToFeature: function (data, dataProjection) {
+ let format = new GeoSJON();
+ let feature = format.readFeature(data, {
+ dataProjection: dataProjection || GisApp.TO_DATA_PROJ,
+ featureProjection: GisApp.TO_PROJ,
+ });
+ return feature;
+ },
+ convertGeoJsonToFeatures: function (data, dataProjection) {
+ let format = new GeoSJON();
+ const features = [];
+ for (let obj of data) {
+ let feature = null;
+ try {
+ feature = format.readFeature(obj, {
+ dataProjection: dataProjection || GisApp.TO_DATA_PROJ,
+ featureProjection: GisApp.TO_PROJ,
+ });
+ } catch (error) {
+ console.log(obj);
+ }
+ features.push(feature);
+ }
+ return features;
+ },
+
+ /**
+ * [사용]WKT포맷 Geometry로 feature 생성
+ */
+ convertWktToFeature: function (wtkGeom, dataProjection) {
+ let format = new WKT();
+ let feature = format.readFeature(wtkGeom, {
+ dataProjection: dataProjection || GisApp.TO_DATA_PROJ,
+ featureProjection: GisApp.TO_PROJ,
+ });
+ return feature;
+ },
+
+ /**
+ * [사용]geometry를 WKT포맷으로 변경
+ */
+ convertGeomToWkt: function (geometry) {
+ let format = new WKT();
+ let wktGeom = format.writeGeometry(geometry, {
+ dataProjection: GisApp.TO_DATA_PROJ,
+ featureProjection: GisApp.TO_PROJ,
+ });
+ return wktGeom;
+ },
+ /**
+ * [사용]레이어 전체 표시 컨트롤
+ */
+ setAllLayersOnMap: function (visible) {
+ this.map.getLayers().forEach(function (el) {
+ if (el !== null && el.get('id') !== null) {
+ let layerId = el.get('id');
+ if ('base' !== layerId && 'satellite' !== layerId && 'minimap-base' !== layerId) {
+ el.setVisible(visible);
+ }
+ }
+ });
+ },
+ fitExtentZoomFeatures: function (features, zoom, paddingX = 0, paddingY = 0) {
+ if (!features.length) return;
+ // Feature들의 경계를 계산하여 extent 생성
+ const extent = this.getExtentFeatures(features);
+ if (zoom) {
+ //const paddingX = 0; // X축 이동량 (양수는 오른쪽, 음수는 왼쪽)
+ //const paddingY = -10000; // Y축 이동량 (양수는 아래쪽, 음수는 위쪽)
+ extent[0] -= paddingX;
+ extent[1] -= paddingY;
+ this.fitExtent(extent, zoom);
+ } else {
+ this.map.getView().fit(extent, {
+ maxZoom: 18, // 너무 확대되지 않도록 최대 줌 제한
+ duration: 500, // 부드러운 애니메이션 효과 (밀리초 단위)
+ });
+ }
+ },
+ getExtentFeatures: function (features) {
+ if (!features.length) return;
+ let extent = [];
+ const allCoordinates = features
+ .map(feature => {
+ const geometry = feature.getGeometry();
+ if (!geometry || typeof geometry.getCoordinates !== 'function') {
+ return null; // geometry가 없거나 getCoordinates() 메서드가 없으면 null 반환
+ }
+ return geometry.getCoordinates();
+ })
+ .filter(coords => coords !== null);
+ function extractCoordinatePairs(arr) {
+ let result = [];
+
+ function recursiveFlatten(subArray) {
+ if (!Array.isArray(subArray)) return;
+
+ for (let item of subArray) {
+ if (Array.isArray(item) && item.length === 2 && typeof item[0] === 'number' && typeof item[1] === 'number') {
+ // [숫자, 숫자] 형태의 배열이면 추가
+ result.push(item);
+ } else {
+ // 그렇지 않다면 재귀 호출
+ recursiveFlatten(item);
+ }
+ }
+ }
+
+ recursiveFlatten(arr);
+ return result;
+ }
+
+ const flattenCoord = extractCoordinatePairs(allCoordinates);
+ extent = boundingExtent(flattenCoord);
+ return extent;
+ },
+};
+
+/**
+ * [사용]피처 레이어 검색
+ */
+Feature.prototype.getLayer = function () {
+ let map = GisApp.Module.core.map;
+ let this_ = this,
+ layer_;
+ let sameFeature = function (feature) {
+ return this_ === feature ? true : false;
+ };
+ map.getLayers().forEach(function (layer) {
+ let source = layer.getSource();
+ if (source instanceof VectorSource) {
+ let features = source.getFeatures();
+ if (features.length > 0) {
+ let found = features.some(sameFeature);
+ if (found) {
+ layer_ = layer;
+ }
+ }
+ }
+ });
+ return layer_;
+};
+
+export default GisApp;
diff --git a/packages/common/business_components/src/ui/map/lib/gis.edit.js b/packages/common/business_components/src/ui/map/lib/gis.edit.js
new file mode 100644
index 000000000..ce3fae555
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/gis.edit.js
@@ -0,0 +1,521 @@
+import GisApp from './gis.core';
+import Draw from 'ol/interaction/Draw.js';
+import Modify from 'ol/interaction/Modify.js';
+import Collection from 'ol/Collection.js';
+import Snap from 'ol/interaction/Snap.js';
+import { click, always } from 'ol/events/condition';
+import UndoRedo from 'ol-ext/interaction/UndoRedo.js';
+import Transform from 'ol-ext/interaction/Transform.js';
+import { gAlert } from './util';
+import VectorSource from 'ol/source/Vector.js';
+import VectorLayer from 'ol/layer/Vector';
+
+const GisOlEditLayer = function (options) {
+ this.initialize(options);
+};
+
+GisOlEditLayer.prototype = {
+ defaults: {},
+ editFlag: false,
+ draw: null,
+ modify: null,
+ snap: null,
+ undoredo: null,
+ transform: null,
+ drawType: {
+ point: 'Point',
+ line: 'MultiLineString',
+ poligon: 'MultiPolygon',
+ },
+ selectedLayerId: null, //셀렉트 박스에서 선택된 레이어 ID
+ targetElement: null,
+ editFeatureInfo: {},
+ addFeatureNo: 1,
+ initialize: function (options) {
+ Object.assign(this.defaults, options);
+ },
+ /**
+ * [공통] Interaction 추가
+ */
+ addInteration: function () {
+ this.setUndoInteraction();
+ },
+ /**
+ * [공통] Interaction 삭제
+ */
+ removeInteraction: function () {
+ this.removeDraw();
+ this.removeModify();
+ this.removeSnap();
+ this.clearUndoRedoFeature();
+ },
+ /**
+ * [공통] Interaction Active 컨트롤
+ */
+ setInteractionActive: function (active) {
+ this.setDrawActive(active);
+ this.setModifyActive(active);
+ this.setActiveUndo(active);
+ },
+ /**
+ * [그리기] 기능 추가
+ */
+ setDraw: function (lyrId) {
+ if (!lyrId) return '레이어를 선택해주십시오.';
+ const core = GisApp.Module.core;
+ const layer = core.getLayerById(lyrId);
+ const layerCode = GisApp.layerCode[lyrId];
+ if (!layer) return '트리에서 레이어 체크 후 추가 가능합니다.';
+ const lyrTy = layerCode.gobjTy;
+
+ //초기화
+ this.removeModify();
+ this.removeTransform();
+ if (GisApp.Module.select) {
+ GisApp.Module.select.setActive(false);
+ }
+
+ const originalSource = layer.getSource();
+ const drawSource = new VectorSource({
+ features: originalSource.getFeatures(), // 기존 feature들 복사
+ });
+
+ // 새로운 임시 레이어 추가 (draw 전용)
+ const drawLayer = new VectorLayer({
+ source: drawSource,
+ minZoom: 1, // 이 레이어만 minZoom을 1로 설정
+ style: layer.getStyle(),
+ });
+ core.map.addLayer(drawLayer);
+
+ //그리기 기능 추가
+ const me = this;
+
+ if (me.draw) {
+ core.map.removeInteraction(me.draw);
+ }
+ const drawType = this.drawType[lyrTy];
+ this.draw = new Draw({
+ type: drawType,
+ source: drawSource,
+ condition: function (evt) {
+ return true;
+ },
+ });
+ core.map.addInteraction(this.draw);
+
+ //그리기 기능 이벤트
+ this.draw.on('drawstart', function (event) {});
+
+ const drawFeatureCallback = function (event) {
+ me.setActiveUndo(true);
+ const drawFeature = event.feature;
+ drawFeature.setId(lyrId + '_feature_' + me.addFeatureNo++);
+
+ const properties = {};
+ properties['lyr_id'] = lyrId;
+ drawFeature.setProperties(properties);
+
+ me.setAddFeatureUndo(layer, drawFeature);
+ me.setActiveUndo(false);
+ };
+
+ this.draw.on('drawend', function (event) {
+ drawFeatureCallback(event);
+ });
+
+ /*drawSource.listeners_.addfeature = null;
+ let addfeatureCallback = function(event){
+ }
+ drawSource.on('addfeature', addfeatureCallback);*/
+ },
+ /**
+ * [그리기] 기능 제거
+ */
+ removeDraw: function () {
+ let core = GisApp.Module.core;
+ core.map.removeInteraction(this.draw);
+ if (GisApp.Module.select) {
+ GisApp.Module.select.setActive(true);
+ }
+ },
+ /**
+ * [그리기] 기능 활성화
+ */
+ setDrawActive: function (active) {
+ if (this.draw) {
+ this.draw.setActive(active);
+ if (!active) {
+ /* $('#selectEditLayer').css('display', 'none');
+ $('.btn_edit_add').removeClass('active'); */
+ }
+ }
+ },
+ /**
+ * [히스토리] UndoInteraction 추가
+ */
+ setUndoInteraction: function () {
+ let core = GisApp.Module.core;
+ if (!this.undoredo) {
+ this.undoredo = new UndoRedo();
+ core.map.addInteraction(this.undoredo);
+ }
+ },
+ /**
+ * [히스토리] UndoInteraction 제거
+ */
+ removeUndoInteraction: function () {
+ let core = GisApp.Module.core;
+ if (this.undoredo) {
+ core.map.removeInteraction(this.undoredo);
+ this.undoredo = null;
+ }
+ },
+ /**
+ * [히스토리] UndoInteraction 활성화
+ */
+ setActiveUndo: function (active) {
+ if (this.undoredo) {
+ this.undoredo.setActive(active);
+ }
+ },
+ /**
+ * [히스토리] 피처 추가
+ */
+ setAddFeatureUndo: function (layer, addFeature) {
+ if (this.undoredo) {
+ let obj = {};
+ obj.target = layer.getSource();
+ obj.feature = addFeature;
+ obj.type = 'addfeature';
+ obj.feature.set('uploadType', 'addFeature');
+ this.undoredo._onAddRemove(obj);
+ }
+ },
+ /**
+ * [히스토리] 피처 삭제
+ */
+ setRemoveFeatureUndo: function (layer, removeFeature) {
+ if (this.undoredo) {
+ let obj = {};
+ obj.target = layer.getSource();
+ obj.feature = removeFeature;
+ obj.type = 'removefeature';
+ if (obj.feature.get('uploadType') !== 'addFeature') {
+ obj.feature.set('uploadType', 'removeFeature');
+ }
+ this.undoredo._onAddRemove(obj);
+ }
+ },
+ /**
+ * [히스토리] 이전
+ */
+ undo: function () {
+ if (this.undoredo) {
+ if (this.undoredo.hasUndo() === 0) {
+ gAlert('INFO', 'NO_PREV_HISTORY');
+ return;
+ }
+ this.setActiveUndo(true);
+ this.undoredo.undo();
+ this.setActiveUndo(false);
+ }
+ },
+ /**
+ * [히스토리] 다음
+ */
+ redo: function () {
+ if (this.undoredo) {
+ if (this.undoredo.hasRedo() === 0) {
+ gAlert('INFO', 'NO_NEXT_HISTORY');
+ return;
+ }
+ this.setActiveUndo(true);
+ this.undoredo.redo();
+ this.setActiveUndo(false);
+ }
+ },
+ /**
+ * [히스토리] 초기화
+ */
+ clearUndoRedo: function () {
+ if (this.undoredo) {
+ const undoStack = this.undoredo._undoStack.array_;
+ for (let i in undoStack) {
+ const undo = undoStack[i];
+ if (undo.feature) {
+ undo.feature.set('uploadType', null);
+ }
+ }
+ this.undoredo.clear();
+ }
+ },
+ /**
+ * [히스토리] 피처 초기화
+ */
+ clearUndoRedoFeature: function () {
+ if (this.undoredo) {
+ this.setActiveUndo(true);
+ const undoStack = [...this.undoredo._undoStack.array_];
+ for (let i in undoStack) {
+ const undo = undoStack[i];
+ if (undo.feature) {
+ this.undoredo.undo();
+ }
+ }
+ this.setActiveUndo(false);
+ this.undoredo.clear();
+ }
+ },
+ /**
+ * [삭제] 피처 삭제
+ */
+ deleteFeature: function () {
+ //초기화
+ this.removeDraw();
+ this.removeModify();
+ this.removeTransform();
+
+ //삭제 기능 실행
+ let me = this;
+ let core = GisApp.Module.core;
+ let select = GisApp.Module.select;
+ select.getSelectedFeatures().forEach(function (selectedFeature) {
+ let selectedFeatureId = selectedFeature.getId();
+ let layers = core.map.getLayers();
+ for (let i in layers.array_) {
+ let layer = layers.array_[i];
+ if (layer.get('mdfcnYn') === 'Y' && select.getVisible(layer)) {
+ layer
+ .getSource()
+ .getFeatures()
+ .forEach(function (feature) {
+ let featureId = feature.getId();
+ if (selectedFeatureId === featureId) {
+ me.setActiveUndo(true);
+ feature.setProperties({ lyr_id: layer.get('id') });
+ layer.getSource().removeFeature(feature);
+ me.setRemoveFeatureUndo(layer, feature);
+ me.setActiveUndo(false);
+ }
+ });
+ }
+ }
+ });
+ },
+ /**
+ * [수정] 피처 수정 기능 셋팅
+ */
+ setModify: function () {
+ //초기화
+ this.removeDraw();
+ this.removeTransform();
+
+ //수정기능 추가
+ let me = this;
+ let core = GisApp.Module.core;
+ let select = GisApp.Module.select;
+ let selectedFeatures = select.getSelectedFeatures();
+
+ this.modify = new Modify({
+ features: selectedFeatures,
+ });
+ core.map.addInteraction(this.modify);
+
+ select.selectInteraction.on('change:active', function () {
+ selectedFeatures.forEach(function (each) {
+ selectedFeatures.remove(each);
+ });
+ });
+
+ this.modify.on('modifystart', function (event) {});
+
+ this.modify.on('modifyend', function (event) {
+ let features = event.features.getArray();
+ for (let i = 0; i < features.length; i++) {
+ let feature = features[i];
+ let layer = select.selectInteraction.getLayer(feature);
+ feature.setProperties({ lyr_id: layer.get('id') });
+ }
+ });
+ },
+ /**
+ * [수정] 피처 수정 기능 제거
+ */
+ removeModify: function () {
+ if (this.modify) {
+ let core = GisApp.Module.core;
+ core.map.removeInteraction(this.modify);
+ this.modify = null;
+ }
+ },
+ /**
+ * [수정] 기능 활성화
+ */
+ setModifyActive: function (active) {
+ if (this.modify) {
+ this.modify.setActive(active);
+ }
+ },
+ /**
+ * [크기조절] 피처 크기조절 기능 셋팅
+ */
+ setTransform: function () {
+ //초기화
+ this.removeDraw();
+ this.removeModify();
+
+ let me = this;
+ let core = GisApp.Module.core;
+ let select = GisApp.Module.select;
+
+ this.transform = new Transform({
+ enableRotatedTransform: false,
+ addCondition: click,
+ filter: function (f, l) {
+ return f.getGeometry().getType() !== 'Point';
+ },
+ // layers: [vector],
+ hitTolerance: select.defaults.hitTolerance,
+ translateFeature: true,
+ scale: true,
+ rotate: true,
+ keepAspectRatio: always,
+ translate: true,
+ stretch: true,
+ });
+ this.transform.on(['rotateend', 'translateend', 'scaleend'], function (event) {
+ let features = event.features.getArray();
+ for (let i = 0; i < features.length; i++) {
+ let feature = features[i];
+ let layer = feature.getLayer();
+ feature.setProperties({ lyr_id: layer.get('id') });
+ }
+ });
+
+ core.map.addInteraction(this.transform);
+ },
+ /**
+ * [크기조절] 피처 크기조절 기능 제거
+ */
+ removeTransform: function () {
+ if (this.transform) {
+ let core = GisApp.Module.core;
+ core.map.removeInteraction(this.transform);
+ this.transform = null;
+ }
+ },
+ /**
+ * [크기조절] 피처 크기조절 기능 활성화
+ */
+ setTransformActive: function (active) {
+ if (this.transform) {
+ this.transform.setActive(active);
+ }
+ },
+ /**
+ * [스냅] 기능 추가
+ */
+ setSnap: function (active) {
+ //초기화
+ this.removeSnap();
+
+ //스냅 기능 추가
+ let core = GisApp.Module.core;
+ let select = GisApp.Module.select;
+ core.map.removeInteraction(this.snap);
+ let features = [];
+ let layers = core.map.getLayers();
+ for (let i in layers.array_) {
+ let layer = layers.array_[i];
+ if (select.getVisible(layer) && layer.get('type') !== 'base') {
+ if (layer.getSource().getFeatures().length > 0) {
+ features = features.concat(layer.getSource().getFeatures());
+ }
+ }
+ }
+ let snapCollection = new Collection(features);
+ this.snap = new Snap({
+ features: snapCollection,
+ pixelTolerance: 30,
+ });
+ core.map.addInteraction(this.snap);
+ },
+ /**
+ * [스냅] 기능 제거
+ */
+ removeSnap: function () {
+ if (this.snap) {
+ let core = GisApp.Module.core;
+ core.map.removeInteraction(this.snap);
+ this.snap = null;
+ }
+ },
+ /**
+ * [스냅] 스냅에 피처 추가
+ */
+ addSnap: function (layers) {
+ let me = this;
+ if (me.snap) {
+ for (let i in layers) {
+ let source = layers[i].getSource();
+ source.listeners_.addfeature = null;
+ source.on('addfeature', function (event) {
+ me.snap.addFeature(event.feature);
+ });
+ }
+ }
+ },
+ /**
+ * [스냅] 기능 활성화
+ */
+ setSnapActive: function (active) {
+ if (this.snap) {
+ this.snap.setActive(active);
+ }
+ },
+
+ /**
+ * [저장] 피처 수정 정보 셋팅
+ */
+ setEditFeatureInfo: function (flag, feature) {
+ const featureId = feature.getId();
+ const properties = feature.getProperties();
+
+ //신규 추가 후 삭제시에 editFeatureInfo에서 제거
+ if (properties.uploadType === 'addFeature' && flag === 'delete') {
+ delete this.editFeatureInfo[featureId];
+ return;
+ }
+
+ //좌표값 WKT포멧으로 변경
+ const pGeom = GisApp.Module.core.convertFeatureGeomToWKT(feature);
+ const param = {
+ flag,
+ ...properties,
+ geom: pGeom,
+ };
+ this.editFeatureInfo[featureId] = param;
+ },
+ getEditFeatureInfo: function () {
+ const undoStack = this.undoredo._undoStack.array_;
+ this.editFeatureInfo = {};
+ for (let i in undoStack) {
+ const undo = undoStack[i];
+ if (undo.feature) {
+ const feature = undo.feature;
+ const properties = feature.getProperties();
+ if (properties.uploadType === 'addFeature') {
+ this.setEditFeatureInfo('insert', feature);
+ } else if (properties.uploadType === 'removeFeature') {
+ this.setEditFeatureInfo('delete', feature);
+ } else if (undo.type === 'changegeometry') {
+ this.setEditFeatureInfo('update', feature);
+ }
+ }
+ }
+ return this.editFeatureInfo;
+ },
+};
+
+export default GisOlEditLayer;
diff --git a/packages/common/business_components/src/ui/map/lib/gis.layer.js b/packages/common/business_components/src/ui/map/lib/gis.layer.js
new file mode 100644
index 000000000..3e0f6d1a1
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/gis.layer.js
@@ -0,0 +1,557 @@
+import Feature from 'ol/Feature.js';
+import GeoJSON from 'ol/format/GeoJSON.js';
+import Point from 'ol/geom/Point.js';
+import VectorLayer from 'ol/layer/Vector';
+import VectorImageLayer from 'ol/layer/VectorImage.js';
+import { all, bbox } from 'ol/loadingstrategy';
+import { transform } from 'ol/proj';
+import Cluster from 'ol/source/Cluster.js';
+import VectorSource from 'ol/source/Vector.js';
+import VectorTile from 'ol/source/VectorTile.js';
+import CircleStyle from 'ol/style/Circle.js';
+import Fill from 'ol/style/Fill.js';
+import Icon from 'ol/style/Icon.js';
+import Stroke from 'ol/style/Stroke.js';
+import Style from 'ol/style/Style.js';
+import Text from 'ol/style/Text.js';
+import { createXYZ } from 'ol/tilegrid';
+import GisApp from './gis.core.js';
+import GisOlStyle from './gis.style.js';
+import Tile from 'ol/layer/Tile.js';
+import TileWMS from 'ol/source/TileWMS.js';
+import gisCore from './gis.core.js';
+
+/**
+ * GisApp.layer
+ * @requires gis.core.js
+ * @requires gis.style.js
+ */
+const GisOlLayer = function (options) {
+ this.initialize(options);
+};
+
+GisOlLayer.prototype = {
+ layers: [],
+ cesiumLayers: [],
+ defaults: {},
+ storageNm: 'hasudo:',
+ defaultLayerOptions: {
+ name: '',
+ zIndex: 2,
+ opacity: 1,
+ flterSql: null,
+ minZoom: '15',
+ },
+ initialize: function (options) {
+ Object.assign(this.defaults, options);
+ this.gisStyle = new GisOlStyle();
+ GisApp.Module.style = this.gisStyle;
+ },
+ gisStyle: null,
+ /**
+ * [사용]빈 레이어 생성
+ */
+ makeLayer: function (options) {
+ let layerOptions = {
+ ...this.defaultLayerOptions,
+ ...options,
+ minZoom: options?.minViewSize ? options?.minViewSize : options?.minZoom,
+ zIndex: options?.drawOrdr ? options?.drawOrdr : 1,
+ };
+ let returnLayer = new VectorImageLayer(layerOptions);
+ this.layers.push(returnLayer);
+ return returnLayer;
+ },
+ /**
+ * gisStyle 반환
+ */
+ getGisStyle: function () {
+ return this.gisStyle;
+ },
+ /**
+ * [사용]WFS 벡터 소스 반환
+ */
+ getVectorSourceGeoserver: function (options) {
+ const url = GisApp.GEOSERVER_URL;
+ const toProj = GisApp.TO_PROJ;
+ const lyrTyNm = options.lyrTyNm;
+ const params = {};
+ params['TYPENAME'] = this.storageNm + lyrTyNm;
+ params['SERVICE'] = 'WFS';
+ params['VERSION'] = '1.1.0';
+ params['REQUEST'] = 'GetFeature';
+ params['SRS'] = toProj;
+ params['OUTPUTFORMAT'] = 'application/json';
+ if (options?.flterSql && options?.flterSql?.trim() !== 'null') {
+ params['CQL_FILTER'] = options?.flterSql;
+ }
+ const strParams = new URLSearchParams(params).toString();
+ let source = new VectorSource({
+ format: new GeoJSON(),
+ url: function (extent) {
+ return url + '?' + strParams;
+ },
+ strategy: all,
+ });
+ return source;
+ },
+ /**
+ * [사용]WFS 벡터 소스 반환
+ */
+ getVectorTileSource: function (options) {
+ let url = GisApp.GEOSERVER_URL;
+ let toProj = GisApp.TO_PROJ;
+ let params = {};
+ params['TYPENAME'] = this.storageNm + options.typeName;
+ params['SERVICE'] = 'WFS';
+ params['VERSION'] = '1.1.0';
+ params['REQUEST'] = 'GetFeature';
+ params['SRS'] = toProj;
+ params['OUTPUTFORMAT'] = 'application/json';
+ let strParams = new URLSearchParams(params).toString();
+
+ let source = new VectorTile({
+ format: new GeoJSON(),
+
+ tileUrlFunction: function (tileCoord, pixelRatio, projection) {
+ let filterStr = '&CQL_FILTER=bbox(GEOM,' + source.getTileGrid().getTileCoordExtent(tileCoord).join(',') + ",'" + projection.getCode() + "')";
+ if (options.flterSql) {
+ filterStr += ' AND ' + options.flterSql;
+ }
+ let paramWithUrl = url + '?' + strParams + filterStr;
+ return paramWithUrl;
+ },
+ tileGrid: createXYZ,
+ });
+
+ return source;
+ },
+ /**
+ * [사용]WFS 레이어 생성
+ */
+ getWfsLayer: function (option) {
+ option = option || {};
+ //레이어 생성
+ let returnLayer = this.makeLayer(option);
+ //레이어 소스 생성
+ let source = this.getVectorSourceGeoserver(option);
+ returnLayer.setSource(source);
+ //스타일 추가
+ returnLayer.setStyle(this.gisStyle.getLayerStyleFunction(returnLayer, option));
+ returnLayer.set('id', option.id + '');
+ return returnLayer;
+ },
+ /**
+ * [사용]WMS 레이어 생성
+ */
+ getWmsLayer: function (options) {
+ let layerOptions = Object.assign({}, this.defaultLayerOptions, options);
+ let url = GisApp.GEOSERVER_WMS_URL;
+ let toProj = GisApp.TO_PROJ;
+ const lyrTyNm = options.lyrTyNm;
+ let params = {};
+ params['LAYERS'] = this.storageNm + lyrTyNm;
+ params['SERVICE'] = 'WMS';
+ params['VERSION'] = '1.1.1';
+ params['REQUEST'] = 'GetMap';
+ params['SRS'] = toProj;
+ params['FORMAT'] = 'image/png';
+ params['TILED'] = true;
+ const wmsLayer = new Tile({
+ ...layerOptions,
+ id: layerOptions?.id,
+ source: new TileWMS({
+ url,
+ params,
+ serverType: 'geoserver',
+ }),
+ });
+ wmsLayer.setZIndex(options.draw0rdr);
+ wmsLayer.setMinZoom(options.minViewSize);
+ this.layers.push(wmsLayer);
+ return wmsLayer;
+ },
+ /**
+ * [사용]WFS 레이어 생성
+ */
+ getWfsEmptyLayer: function (option) {
+ option = option || {};
+ //레이어 생성
+ let returnLayer = this.makeLayer(option);
+ let vectorSource = new VectorSource({
+ strategy: all,
+ });
+ returnLayer.setSource(vectorSource);
+ //스타일 추가
+ returnLayer.setStyle(this.gisStyle.getLayerStyle(returnLayer, option));
+ returnLayer.set('id', option.id + '');
+ let map = GisApp.Module.core.map;
+ map.addLayer(returnLayer);
+ return returnLayer;
+ },
+ /**
+ * 피처 리스트로 레이어 생성
+ */
+ getLayerByFeature: function (feature, layerOptions, layerStyle) {
+ if (!layerOptions || !layerOptions.id) {
+ return null;
+ }
+ let vectorSource = new VectorSource({
+ strategy: all,
+ });
+ if (feature) {
+ if (Object.prototype.toString.call(feature).slice(8, -1).indexOf('Object') > -1) {
+ vectorSource.addFeature(feature);
+ } else {
+ vectorSource.addFeatures(feature);
+ }
+ }
+ GisApp.Module.core.deleteLayer(layerOptions.id);
+ layerOptions.source = vectorSource;
+ let layer = new VectorImageLayer(layerOptions);
+ layer.setStyle(layerStyle);
+ layer.set('id', layerOptions.id + '');
+ return layer;
+ },
+ /**
+ * 피처 하이라이트
+ */
+ highLightFeature: function (hlFeature, layerOptions, styleOptions, customStyleFunction) {
+ const color = styleOptions.color || 'rgba(135, 116, 243, 0.4)';
+ const width = styleOptions.width || 10;
+ const featureKey = styleOptions.featureKey || '';
+ const fitExtent = layerOptions.fitExtent || 'true';
+
+ const styleFunction = function (feature, resolution) {
+ const featureStyle = feature.getStyle();
+ if (featureStyle) return null;
+ const style = new Style({
+ image: new CircleStyle({
+ fill: new Fill({
+ color: color,
+ }),
+ radius: width,
+ }),
+ stroke: new Stroke({
+ color: color,
+ width: width,
+ }),
+ });
+
+ const getFeatureText = function (feature) {
+ if (featureKey) {
+ let text = feature.get(featureKey);
+ if (text) return text;
+ }
+ return '';
+ };
+ const text = new Text({
+ stroke: new Stroke({
+ color: 'white',
+ width: 2,
+ }),
+ fill: new Fill({
+ color: 'black',
+ }),
+ font: '12px Verdana',
+ text: getFeatureText(feature),
+ });
+ style.setText(text);
+ return style;
+ };
+ let actualStyleFunction = customStyleFunction || styleFunction;
+ GisApp.Module.core.deleteLayer(layerOptions.id);
+ let layer = this.getLayerByFeature(hlFeature, layerOptions, actualStyleFunction);
+ if (hlFeature && hlFeature.length > 0) {
+ let map = GisApp.Module.core.map;
+ map.addLayer(layer);
+ if (fitExtent === 'true') {
+ GisApp.Module.core.fitExtent(layer.getSource().getExtent());
+ }
+ }
+ return layer;
+ },
+ /**
+ * 마커 레이어 맵에 추가
+ */
+ addMarkerLayer: function (lat, lon, fromProj, layerOptions) {
+ let p = transform([Number(lon), Number(lat)], fromProj, GisApp.TO_PROJ);
+ let feature = new Feature({
+ geometry: new Point(p),
+ });
+ let layer = this.getMarkerLayer(layerOptions);
+ layer.getSource().addFeature(feature);
+ let map = GisApp.Module.core.map;
+ map.addLayer(layer);
+ GisApp.Module.core.fitExtent(feature.getGeometry().getExtent());
+ return layer;
+ },
+ /**
+ * 마커 레이어 생성
+ */
+ getMarkerLayer: function (layerOptions) {
+ let imgSrc = GisApp.CONTEXT_PATH + 'images/marker/marker-icon.png';
+ let style = new Style({
+ image: new Icon({
+ anchor: [0.5, 46],
+ anchorXUnits: 'fraction',
+ anchorYUnits: 'pixels',
+ src: imgSrc,
+ }),
+ });
+ let layer = this.getLayerByFeature(null, layerOptions, style);
+ return layer;
+ },
+ /**
+ * 피처 생성
+ */
+ getFeatureCoord: function (coord) {
+ let feature = new Feature({
+ geometry: new Point(coord),
+ });
+ return feature;
+ },
+ /**
+ * 피처 생성(lat,lon)
+ */
+ getFeatureLonLat: function (lat, lon) {
+ let p = transform([Number(lon), Number(lat)], GisApp.FROM_PROJ, GisApp.TO_PROJ);
+ let feature = new Feature({
+ geometry: new Point(p),
+ });
+ return feature;
+ },
+ addPolygonLayer: function (features, option) {
+ const map = GisApp.Module.core.map;
+ option = Object.assign(
+ {
+ gobjTy: 'lineLabelPoligon',
+ minZoom: option?.minZoom || '10',
+ lblAtrbNm: option?.lblAtrbNm || 'fclty_nm',
+ lnWdth: option?.lnWdth || '2',
+ lnColor: option?.lnColor || 'rgba(0, 0, 0, 1)',
+ polgnColor: option?.polgnColor || 'rgba(255, 0, 0, 0.2)',
+ clickPsblYn: option?.clickPsblYn || 'N',
+ fondNm: '18px "맑은 고딕",Malgun Gothic',
+ },
+ option
+ );
+ features[0].set('overLayId', option?.overlayId || null);
+ features[0].set('html', option?.html || null);
+ //레이어 생성
+ let vectorLayer = this.makeLayer(option);
+ //레이어 소스 생성
+ const vectorSource = new VectorSource({});
+ vectorSource.addFeatures(features);
+ vectorLayer.setSource(vectorSource);
+ //스타일 추가
+ vectorLayer.setStyle(this.gisStyle.getLayerStyleFunction(vectorLayer, option));
+ vectorLayer.set('id', option?.id + '');
+ GisApp.Module.core.deleteLayer(option?.id);
+ map.addLayer(vectorLayer);
+ },
+ addLayerByLyrTyNm: function (id, lyrTyNm, addQurryFlter) {
+ const map = GisApp.Module.core.map;
+ const layerCode = GisApp.layerCode;
+ const matched = Object.values(layerCode).find(obj => obj.lyrTyNm === lyrTyNm);
+ matched['id'] = id;
+ let layer = GisApp.Module.core.getLayerById(matched?.id);
+ if (layer) {
+ layer.setVisible(true);
+ } else {
+ const queryFlter = layerCode?.flterSql || '' + ' ' + (addQurryFlter || '');
+ matched['flterSql'] = queryFlter;
+ layer = this.getWfsLayer(matched);
+ GisApp.Module.core.deleteLayer(id);
+ map.addLayer(layer);
+ }
+ },
+ /**
+ * 클러스터 레이어 맵에 추가
+ */
+ addClusterLayer: function (features, { layerId, color }) {
+ const map = GisApp.Module.core.map;
+ const vectorSource = new VectorSource({});
+ vectorSource.addFeatures(features);
+ const clusterLayerId = layerId + 'Cluster';
+ const vectorLayerId = layerId + 'Vector';
+ // 클러스터 레이어 생성
+ const clusterLayer = new VectorLayer({
+ id: clusterLayerId,
+ source: new Cluster({
+ distance: 100, // 클러스터링 거리
+ source: vectorSource,
+ geometryFunction: function (feature) {
+ let geometry = feature.getGeometry();
+ let type = geometry.getType();
+ let coordinates;
+ if (type === 'Point') {
+ coordinates = geometry.getCoordinates();
+ } else if (type === 'LineString' || type === 'MultiLineString') {
+ let totalX = 0;
+ let totalY = 0;
+ let totalPoints = 0;
+ geometry.getCoordinates().forEach(line => {
+ line.forEach(point => {
+ totalX += point[0];
+ totalY += point[1];
+ totalPoints++;
+ });
+ });
+ coordinates = [totalX / totalPoints, totalY / totalPoints];
+ }
+ return new Point(coordinates);
+ },
+ }),
+ style: feature => {
+ const clusterSize = feature.get('features').length;
+ return new Style({
+ image: new CircleStyle({
+ radius: 10,
+ fill: new Fill({ color }),
+ }),
+ stroke: new Stroke({
+ color,
+ width: 10,
+ }),
+ text: new Text({
+ text: clusterSize.toString(),
+ fill: new Fill({
+ color: 'white',
+ }),
+ stroke: new Stroke({
+ color: 'black',
+ width: 4,
+ }),
+ }),
+ });
+ },
+ });
+
+ const vectorLayer = new VectorLayer({
+ id: vectorLayerId,
+ zIndex: 1,
+ source: vectorSource,
+ style: new Style({
+ image: new CircleStyle({
+ radius: 10,
+ fill: new Fill({ color }),
+ }),
+ stroke: new Stroke({
+ color,
+ width: 10,
+ }),
+ }),
+ });
+ GisApp.Module.core.deleteLayer(clusterLayerId);
+ GisApp.Module.core.deleteLayer(vectorLayerId);
+ map.addLayer(clusterLayer);
+ map.addLayer(vectorLayer);
+ function handleZoomEnd() {
+ const findClusterLayer = map
+ ?.getLayers()
+ ?.getArray()
+ .find(function (layer) {
+ return layer.get('id') === clusterLayerId;
+ });
+ const findVectorLayer = map
+ ?.getLayers()
+ ?.getArray()
+ .find(function (layer) {
+ return layer.get('id') === vectorLayerId;
+ });
+ if (!findClusterLayer) {
+ GisApp.Module.core.deleteLayer(vectorLayerId);
+ }
+ if (!findVectorLayer) {
+ GisApp.Module.core.deleteLayer(vectorLayerId);
+ }
+ const zoomLevel = map.getView().getZoom();
+ if (zoomLevel >= 18) {
+ vectorLayer.setVisible(true);
+ clusterLayer.setVisible(false);
+ } else {
+ clusterLayer.setVisible(true);
+ vectorLayer.setVisible(false);
+ }
+ if (!findClusterLayer && !findVectorLayer) {
+ map.getView().un('change:resolution', handleZoomEnd);
+ }
+ }
+ // 줌 이벤트 핸들러 등록
+ map.getView().un('change:resolution', handleZoomEnd);
+ map.getView().on('change:resolution', handleZoomEnd);
+ handleZoomEnd();
+ },
+ setLayersToCesium: function (ol3dInstance) {
+ let me = this;
+ //const { Cesium } = window;
+ const cesiumScene = ol3dInstance.getCesiumScene(); // Cesium Scene 가져오기
+ me.cesiumLayers.forEach(cesiumLayer => {
+ cesiumScene.imageryLayers.remove(cesiumLayer);
+ });
+ const layers = GisApp.Module.core.map
+ .getAllLayers()
+ .filter(layer => layer.get('type') !== 'base')
+ .sort((a, b) => {
+ const orderA = a.getProperties().drawOrdr || 0;
+ const orderB = b.getProperties().drawOrdr || 0;
+ return orderA - orderB; // 낮은 값이 먼저 오도록 정렬
+ });
+
+ layers.forEach(layer => {
+ const properties = layer.getProperties();
+ let cesiumLayer = null;
+ if (properties?.lyrTyNm) {
+ const visible = layer.getVisible();
+ const lyrTyNm = properties.lyrTyNm;
+ // OpenLayers에서 레이어의 가시성 확인
+ const { Cesium } = window;
+ const wmsUrl = GisApp.GEOSERVER_WMS_URL;
+ const wmsProvider = new Cesium.WebMapServiceImageryProvider({
+ url: wmsUrl,
+ layers: GisApp.Module.layer.storageNm + lyrTyNm, // WMS 레이어 이름
+ parameters: {
+ service: 'WMS',
+ version: '1.1.1',
+ request: 'GetMap',
+ format: 'image/png',
+ transparent: true,
+ styles: lyrTyNm,
+ },
+ });
+ cesiumLayer = cesiumScene.imageryLayers.addImageryProvider(wmsProvider);
+ cesiumLayer['properties'] = properties;
+ me.cesiumLayers.push(cesiumLayer);
+ if (properties?.visible) {
+ const zoomLevel = getOpenLayersZoom(cesiumScene);
+ cesiumLayer.show = zoomLevel >= Number(properties?.minZoom);
+ } else {
+ cesiumLayer.show = false;
+ }
+ }
+ });
+ if (me.cesiumLayers.length > 0) {
+ cesiumScene._moveEndListener = () => {
+ const zoomLevel = getOpenLayersZoom(cesiumScene); // 대략적인 줌 계산
+ me.cesiumLayers.forEach(cesiumLayer => {
+ const properties = cesiumLayer.properties;
+ if (properties?.visible) {
+ console.log('zoomLevel >= minZoom==', properties?.lyrTyNm, zoomLevel >= properties?.minZoom);
+ cesiumLayer.show = zoomLevel >= Number(properties?.minZoom);
+ }
+ });
+ };
+ cesiumScene.camera.moveEnd.addEventListener(cesiumScene._moveEndListener);
+ }
+ },
+};
+
+function getOpenLayersZoom(cesiumScene) {
+ const cameraHeight = cesiumScene.camera.positionCartographic.height;
+ const maxResolution = 156543.03392804097; // OpenLayers 기본 해상도 (Google Mercator 기준)
+ const tileSize = 256; // 기본 타일 크기
+ return Math.ceil(Math.log2(maxResolution / (cameraHeight / tileSize)));
+}
+
+export default GisOlLayer;
diff --git a/packages/common/business_components/src/ui/map/lib/gis.measure.js b/packages/common/business_components/src/ui/map/lib/gis.measure.js
new file mode 100644
index 000000000..6fc427bed
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/gis.measure.js
@@ -0,0 +1,204 @@
+import Style from 'ol/style/Style';
+import VectorLayer from 'ol/layer/Vector';
+import VectorSource from 'ol/source/Vector';
+import Fill from 'ol/style/Fill.js';
+import Stroke from 'ol/style/Stroke';
+import CircleStyle from 'ol/style/Circle';
+import { getLength, getArea } from 'ol/sphere';
+import Draw from 'ol/interaction/Draw';
+import Polygon from 'ol/geom/Polygon';
+import LineString from 'ol/geom/LineString';
+import { unByKey } from 'ol/Observable';
+import Overlay from 'ol/Overlay';
+import GisApp from './gis.core';
+//import DOMPurify from 'dompurify';
+const GisOlMeasure = function (options) {
+ GisApp.measure = {};
+ GisApp.measure.helpTooltipElement = null;
+ GisApp.measure.helpTooltip = null;
+ GisApp.measure.sketch = null;
+ GisApp.measure.measureTooltipElement = null;
+ GisApp.measure.measureTooltip = null;
+ GisApp.measure.continueLineMsg = '선 그리기 종료를 위해 마우스를 더블클릭하세요.';
+ GisApp.measure.Polygon = 'Polygon';
+ GisApp.measure.LineString = 'LineString';
+
+ this.initialize(options);
+};
+
+GisOlMeasure.prototype = {
+ map: null,
+ draw: null,
+ vector: null,
+ type: null,
+ initialize: function (map) {
+ let me = this;
+ let source = new VectorSource();
+ let vector = new VectorLayer({
+ source: source,
+ style: new Style({
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 0.2)',
+ }),
+ stroke: new Stroke({
+ color: '#ffcc33',
+ width: 2,
+ }),
+ image: new CircleStyle({
+ radius: 7,
+ fill: new Fill({
+ color: '#ffcc33',
+ }),
+ }),
+ }),
+ });
+ me.map = map;
+ me.vector = vector;
+ me.map.addLayer(vector);
+ me.map.removeInteraction(this.draw);
+ },
+ pointerMoveHandler: function (evt) {
+ if (evt.dragging) {
+ return;
+ }
+ let helpMsg = '시작점을 선택하세요';
+ if (GisApp.measure.sketch) {
+ helpMsg = GisApp.measure.continueLineMsg;
+ }
+ if (GisApp.measure.helpTooltipElement) {
+ //GisApp.measure.helpTooltipElement.innerHTML = DOMPurify.sanitize(helpMsg);
+ GisApp.measure.helpTooltipElement.innerHTML = helpMsg;
+ GisApp.measure.helpTooltip.setPosition(evt.coordinate);
+ GisApp.measure.helpTooltipElement.classList.remove('hidden');
+ }
+ },
+ formatLength: function (line) {
+ let length = getLength(line);
+ let output;
+ if (length > 1000) {
+ output = Math.round((length / 1000) * 100) / 100 + ' ' + 'km';
+ } else {
+ output = Math.round(length * 100) / 100 + ' ' + 'm';
+ }
+ return output;
+ },
+ formatArea: function (polygon) {
+ let area = getArea(polygon);
+ let output;
+ if (area > 10000) {
+ output = Math.round((area / 1000000) * 100) / 100 + ' ' + 'km2';
+ } else {
+ output = Math.round(area * 100) / 100 + ' ' + 'm2';
+ }
+ return output;
+ },
+ addInteraction: function (type) {
+ let me = this;
+ me.map.removeInteraction(me.draw);
+ me.map.on('pointermove', me.pointerMoveHandler);
+ me.type = type;
+
+ let source = me.vector.getSource();
+ me.draw = new Draw({
+ source: source,
+ type: type,
+ style: new Style({
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 0.2)',
+ }),
+ stroke: new Stroke({
+ color: 'rgba(0, 0, 0, 0.5)',
+ lineDash: [10, 10],
+ width: 2,
+ }),
+ image: new CircleStyle({
+ radius: 5,
+ stroke: new Stroke({
+ color: 'rgba(0, 0, 0, 0.7)',
+ }),
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 0.2)',
+ }),
+ }),
+ }),
+ });
+
+ me.map.addInteraction(me.draw);
+ me.createMeasureTooltip();
+ me.createHelpTooltip();
+
+ let listener;
+ me.draw.on('drawstart', function (evt) {
+ let tooltipCoord = evt.coordinate;
+ GisApp.measure.sketch = evt.feature;
+ listener = GisApp.measure.sketch.getGeometry().on('change', function (evt) {
+ let geom = evt.target;
+ let output;
+ if (geom instanceof Polygon) {
+ output = me.formatArea(geom);
+ tooltipCoord = geom.getInteriorPoint().getCoordinates();
+ } else if (geom instanceof LineString) {
+ output = me.formatLength(geom);
+ tooltipCoord = geom.getLastCoordinate();
+ }
+ //GisApp.measure.measureTooltipElement.innerHTML = DOMPurify.sanitize(output);
+ GisApp.measure.measureTooltipElement.innerHTML = output;
+ GisApp.measure.measureTooltip.setPosition(tooltipCoord);
+ });
+ });
+
+ me.draw.on('drawend', function () {
+ GisApp.measure.measureTooltipElement.className = 'ol-tooltip ol-tooltip-static';
+ GisApp.measure.measureTooltip.setOffset([0, -7]);
+ GisApp.measure.sketch = null;
+ // unset tooltip so that a new one can be created
+ GisApp.measure.measureTooltipElement = null;
+ me.createMeasureTooltip();
+ unByKey(listener);
+ me.removeInteraction();
+ });
+ },
+ removeInteraction: function () {
+ this.map.removeInteraction(this.draw);
+ this.map.removeOverlay(GisApp.measure.helpTooltip);
+ },
+ removeFeature: function () {
+ let source = this.vector.getSource();
+ let features = this.vector.getSource().getFeatures();
+ for (let i in features) {
+ let feature = features[i];
+ source.removeFeature(feature);
+ }
+ },
+ createHelpTooltip: function () {
+ let me = this;
+ if (GisApp.measure.helpTooltipElement) {
+ GisApp.measure.helpTooltipElement.parentNode.removeChild(GisApp.measure.helpTooltipElement);
+ }
+ GisApp.measure.helpTooltipElement = document.createElement('div');
+ GisApp.measure.helpTooltipElement.className = 'ol-tooltip hidden';
+ GisApp.measure.helpTooltip = new Overlay({
+ element: GisApp.measure.helpTooltipElement,
+ offset: [15, 0],
+ positioning: 'center-left',
+ });
+ me.map.addOverlay(GisApp.measure.helpTooltip);
+ },
+ createMeasureTooltip: function () {
+ let me = this;
+ if (GisApp.measure.measureTooltipElement) {
+ GisApp.measure.measureTooltipElement.parentNode.removeChild(GisApp.measure.measureTooltipElement);
+ }
+ GisApp.measure.measureTooltipElement = document.createElement('div');
+ GisApp.measure.measureTooltipElement.className = 'ol-tooltip ol-tooltip-measure';
+ GisApp.measure.measureTooltip = new Overlay({
+ id: 'measure',
+ element: GisApp.measure.measureTooltipElement,
+ offset: [0, -15],
+ positioning: 'bottom-center',
+ });
+ me.map.addOverlay(GisApp.measure.measureTooltip);
+ },
+};
+
+export default GisOlMeasure;
diff --git a/packages/common/business_components/src/ui/map/lib/gis.select.js b/packages/common/business_components/src/ui/map/lib/gis.select.js
new file mode 100644
index 000000000..d48c67a5e
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/gis.select.js
@@ -0,0 +1,407 @@
+import GisApp from './gis.core';
+import _ from 'lodash';
+import Style from 'ol/style/Style.js';
+import { click, mouseOnly, never } from 'ol/events/condition';
+import DragBox from 'ol/interaction/DragBox.js';
+import Draw from 'ol/interaction/Draw.js';
+import Select from 'ol/interaction/Select.js';
+import VectorLayer from 'ol/layer/Vector.js';
+import Overlay from 'ol/Overlay.js';
+import VectorSource from 'ol/source/Vector.js';
+import CircleStyle from 'ol/style/Circle.js';
+import Fill from 'ol/style/Fill.js';
+import Stroke from 'ol/style/Stroke.js';
+//import DOMPurify from 'dompurify';
+
+const GisOlSelect = function (map, options) {
+ GisApp.select = {};
+ GisApp.select.helpTooltipElement = null;
+ GisApp.select.helpTooltip = null;
+ GisApp.select.helpMsg = null;
+ this.initialize(map, options);
+};
+
+GisOlSelect.prototype = {
+ map: null,
+ defaults: {
+ hitTolerance: 10,
+ useAreaControl: false,
+ },
+ selectInteraction: null,
+ dragBox: null,
+ dragPolygon: null,
+ selectedFeatures: [],
+ selectFeatureFlag: false,
+ initialize: function (map, options) {
+ this.map = map;
+ _.extend(this.defaults, options);
+ this.setInteraction();
+ this.pointerMoveEventHandler();
+ },
+ /**
+ * SELECT 이벤트 등록
+ */
+ setInteraction: function () {
+ let me = this;
+ let map = this.map;
+ if (this.selectInteraction) {
+ this.removeInteration();
+ }
+ let baseStyle = new Style({
+ stroke: new Stroke({
+ color: 'rgba(135, 116, 243, 0.4)',
+ width: 10,
+ }),
+ });
+
+ this.selectInteraction = new Select({
+ style: function (feature) {
+ let styles = [];
+ let layer = feature.getLayer();
+ let style = GisApp.Module.style.getLayerStyle(layer, null);
+ if (style) {
+ styles.push(style);
+ }
+ styles.push(baseStyle);
+ return styles;
+ },
+ layers: function (layer) {
+ let clickPsblYn = layer?.get('clickPsblYn');
+ return clickPsblYn === 'Y';
+ },
+ condition: click,
+ hitTolerance: me.defaults.hitTolerance,
+ });
+ map.addInteraction(this.selectInteraction);
+
+ this.selectedFeatures = this.selectInteraction.getFeatures();
+ },
+ /**
+ * SELECT 피처 초기화
+ */
+ reset: function () {
+ let me = this;
+ if (GisApp.Module.edit) {
+ GisApp.Module.edit.setDrawActive(false);
+ }
+ if (GisApp.Module.draw) {
+ GisApp.Module.draw.setDrawActive(false);
+ }
+ me.setActive(true);
+ me.removeHelpTooltip();
+
+ let map = this.map;
+ map.removeInteraction(me.dragBox);
+ map.removeInteraction(me.dragPolygon);
+
+ this.selectInteraction.addCondition_ = never;
+ this.selectFeatureFlag = false;
+
+ if (GisApp.Module.measure) {
+ GisApp.Module.measure.removeFeature();
+ GisApp.Module.core.deleteOverlay('measure');
+ }
+
+ GisApp.Module.core.deleteLayer(GisApp.hLayerId);
+ GisApp.Module.core.deleteLayer(GisApp.hlLayerId);
+
+ this.resetSelectedFeature();
+ },
+ /**
+ * 단일 SELECT 이벤트 등록
+ */
+ setSingleInteration: function () {
+ this.reset();
+ this.selectInteraction.addCondition_ = click;
+ this.selectFeatureFlag = true;
+ },
+ /**
+ * 박스 SELECT 이벤트 등록
+ */
+ setBoxInteration: function () {
+ //초기화
+ this.reset();
+ this.selectFeatureFlag = true;
+
+ let me = this;
+ let map = this.map;
+ me.dragBox = new DragBox({
+ condition: mouseOnly,
+ });
+ map.addInteraction(this.dragBox);
+ GisApp.select.helpMsg = '직사각형 시작점을 클릭하고 드래그하세요.';
+ me.createHelpTooltip();
+ me.dragBox.on('boxend', function () {
+ let rotation = map.getView().getRotation();
+ let oblique = rotation % (Math.PI / 2) !== 0;
+ let candidateFeatures = oblique ? [] : me.selectedFeatures;
+ let extent = me.dragBox.getGeometry().getExtent();
+ let layers = map.getLayers();
+ for (let i in layers.array_) {
+ let layer = layers.array_[i];
+ if (layer?.get('clickPsblYn') == 'Y' && me.getVisible(layer)) {
+ layer.getSource().forEachFeatureIntersectingExtent(extent, function (feature) {
+ candidateFeatures.push(feature);
+ });
+ }
+ }
+
+ if (oblique) {
+ let anchor = [0, 0];
+ let geometry = me.dragBox.getGeometry().clone();
+ geometry.rotate(-rotation, anchor);
+ let extent$1 = geometry.getExtent();
+ candidateFeatures.forEach(function (feature) {
+ let geometry = feature.getGeometry().clone();
+ geometry.rotate(-rotation, anchor);
+ if (geometry.intersectsExtent(extent$1)) {
+ me.selectedFeatures.push(feature);
+ }
+ });
+ }
+ me.selectInteraction.dispatchEvent({
+ type: 'select',
+ selected: me.selectedFeatures,
+ deselected: [],
+ });
+ map.removeInteraction(me.dragBox);
+ });
+
+ me.dragBox.on('boxstart', function () {
+ me.removeHelpTooltip();
+ me.selectedFeatures.clear();
+ });
+ },
+ /**
+ * 폴리곤 SELECT 이벤트 등록
+ */
+ setPolygonInteration: function () {
+ this.reset();
+ this.selectFeatureFlag = true;
+
+ let me = this;
+ let map = this.map;
+
+ //폴리곤 레이어 생성
+ let polygonSource = new VectorSource({
+ useSpatialIndex: false,
+ });
+ let polygonLayer = new VectorLayer({
+ source: polygonSource,
+ });
+ map.addLayer(polygonLayer);
+
+ //폴리곤 draw 객체 생성
+ me.dragPolygon = new Draw({
+ source: polygonSource,
+ type: 'Polygon',
+ condition: mouseOnly,
+ });
+ map.addInteraction(me.dragPolygon);
+
+ //툴팁 추가
+ GisApp.select.helpMsg = '시작점을 클릭하세요.';
+ me.createHelpTooltip();
+
+ me.dragPolygon.on('drawstart', function () {
+ me.removeHelpTooltip();
+ me.selectedFeatures.clear();
+ });
+
+ me.dragPolygon.on('drawend', function (evt) {
+ setTimeout(function () {
+ let polygon = evt.feature.getGeometry();
+ let layers = map.getLayers();
+ for (let i in layers.array_) {
+ let layer = layers.array_[i];
+ if (layer?.get('clickPsblYn') == 'Y' && me.getVisible(layer)) {
+ let features = layer.getSource().getFeatures();
+ for (let j = 0; j < features.length; j++) {
+ if (polygon.intersectsExtent(features[j].getGeometry().getExtent())) {
+ me.selectedFeatures.push(features[j]);
+ }
+ }
+ }
+ }
+ me.selectInteraction.dispatchEvent({
+ type: 'select',
+ selected: me.selectedFeatures,
+ deselected: [],
+ });
+ map.removeInteraction(me.dragPolygon);
+ map.removeLayer(polygonLayer);
+ }, 300);
+ });
+ return me.dragPolygon;
+ },
+ /**
+ * 레이어 추가
+ */
+ addLayer: function (layer) {
+ if (layer) {
+ layer.set('clickPsblYn', 'Y');
+ }
+ },
+ /**
+ * 레이어 삭제
+ */
+ removeLayer: function (layer) {
+ if (layer) {
+ layer.set('clickPsblYn', 'N');
+ }
+ },
+ /**
+ * 선택된 객체 초기화
+ */
+ resetSelectedFeature: function () {
+ const selectedFeatures = this.selectedFeatures;
+ selectedFeatures.forEach(feature => {
+ selectedFeatures.remove(feature);
+ });
+ },
+ /**
+ * SELECT 이벤트 활성화
+ */
+ setActive: function (active) {
+ this.selectInteraction.setActive(active);
+ },
+ /**
+ * SELECT 이벤트 삭제
+ */
+ removeInteration: function () {
+ let map = this.map;
+ map.removeInteraction(this.selectInteraction);
+ this.selectInteraction = null;
+
+ //포인터 변경 삭제
+ this.removePointerMoveEventHandler('pointerMoveForSelect');
+ },
+ /**
+ * 피처 위에 마우스 오버시 포인터 변경
+ */
+ pointerMoveEventHandler: function () {
+ let me = this;
+ let map = this.map;
+ let pointerMoveForSelect = function (evt) {
+ let hit = this.forEachFeatureAtPixel(
+ evt.pixel,
+ function (feature, layer) {
+ let returnFlag = true;
+ if (layer?.get('clickPsblYn') === 'N') {
+ returnFlag = false;
+ }
+ return returnFlag;
+ },
+ { hitTolerance: me.defaults.hitTolerance }
+ );
+
+ if (hit && me.selectInteraction.getActive()) {
+ this.getTargetElement().style.cursor = 'pointer';
+ } else {
+ this.getTargetElement().style.cursor = '';
+ }
+ };
+ map.on('pointermove', pointerMoveForSelect);
+ },
+ /**
+ * 포인터 이벤트 삭제
+ */
+ removePointerMoveEventHandler: function (fnNm) {
+ GisApp.Module.core.removeMapEventFnNm('pointermove', fnNm);
+ },
+ /**
+ * 선택된 피처 정보 반환(callback)
+ */
+ getSelectedInfo: function (callback) {
+ let me = this;
+ this.selectInteraction.on('select', function (e) {
+ if (!e.auto) {
+ let features = me.selectInteraction.getFeatures();
+ let layers = [];
+ for (let i in features.array_) {
+ let feature = features.array_[0];
+ let layer = me.selectInteraction.getLayer(feature);
+ layers.push(layer);
+ }
+ callback(features.array_, layers);
+ }
+ });
+ },
+ /**
+ * 선택된 피처 정보 반환
+ */
+ getSelectedFeatures: function () {
+ return this.selectInteraction.getFeatures();
+ },
+ /**
+ * 마지막으로 선택된 피처 정보 반환
+ */
+ getLastSelectedFeatures: function () {
+ return this.selectInteraction.getFeatures();
+ },
+ /**
+ * 툴팁 생성
+ */
+ createHelpTooltip: function () {
+ let me = this;
+ let map = this.map;
+ GisApp.select.helpTooltipElement = document.createElement('div');
+ GisApp.select.helpTooltipElement.className = 'ol-tooltip hidden';
+ GisApp.select.helpTooltip = new Overlay({
+ element: GisApp.select.helpTooltipElement,
+ offset: [15, 0],
+ positioning: 'center-left',
+ });
+ map.addOverlay(GisApp.select.helpTooltip);
+ map.on('pointermove', me.pointerMoveTooltipHandler);
+ },
+ /**
+ * 툴팁 삭제
+ */
+ removeHelpTooltip: function () {
+ if (GisApp.select.helpTooltipElement) {
+ let map = this.map;
+ this.removePointerMoveEventHandler('pointerMoveTooltipHandler');
+ map.removeOverlay(GisApp.measure.helpTooltip);
+ GisApp.select.helpTooltipElement.remove();
+ }
+ },
+ /**
+ * 툴팁용 포인터 move 이벤트
+ */
+ pointerMoveTooltipHandler: function (evt) {
+ if (evt.dragging) {
+ return;
+ }
+ //GisApp.select.helpTooltipElement.innerHTML = DOMPurify.sanitize(GisApp.select.helpMsg);
+ GisApp.select.helpTooltipElement.innerHTML = GisApp.select.helpMsg;
+ GisApp.select.helpTooltip.setPosition(evt.coordinate);
+ GisApp.select.helpTooltipElement.classList.remove('hidden');
+ },
+ getVisible: function (layer) {
+ let maxZoom = layer.getMaxZoom();
+ let minZoom = layer.getMinZoom();
+ let map = this.map;
+ let curZoom = map.getView().getZoom();
+ if (layer.getVisible() && maxZoom > curZoom && minZoom < curZoom) {
+ return true;
+ }
+ return false;
+ },
+ /**
+ * 피처 셀렉트
+ */
+ selectFeature: function (feature, auto) {
+ let me = this;
+ me.selectedFeatures.clear();
+ me.selectedFeatures.push(feature);
+ me.selectInteraction.dispatchEvent({
+ type: 'select',
+ selected: me.selectedFeatures,
+ deselected: [],
+ auto: auto,
+ });
+ },
+};
+
+export default GisOlSelect;
diff --git a/packages/common/business_components/src/ui/map/lib/gis.style.js b/packages/common/business_components/src/ui/map/lib/gis.style.js
new file mode 100644
index 000000000..df9be2daa
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/gis.style.js
@@ -0,0 +1,402 @@
+import GisApp from './gis.core.js';
+import Style from 'ol/style/Style.js';
+import Fill from 'ol/style/Fill.js';
+import Icon from 'ol/style/Icon.js';
+import Circle from 'ol/style/Circle.js';
+import Stroke from 'ol/style/Stroke.js';
+import Text from 'ol/style/Text.js';
+import Point from 'ol/geom/Point.js';
+import RegularShape from 'ol/style/RegularShape.js';
+import Overlay from 'ol/Overlay';
+import _ from 'lodash';
+//import DOMPurify from 'dompurify';
+import { createPortal } from 'react-dom';
+import ReactDOM from 'react-dom/client';
+import React from 'react';
+
+const GisOlStyle = function () {
+ this.initialize();
+};
+
+GisOlStyle.prototype = {
+ overlays: {},
+ hiddenOverlays: {},
+ initialize: function () {
+ this.layericonPath = GisApp.CONTEXT_LAYER_PATH;
+ },
+ getRGBAToString: function (r, g, b, a) {
+ let rgba = 'rgba({r}, {g}, {b}, {a})';
+ return rgba.replace('{r}', r).replace('{g}', g).replace('{b}', b).replace('{a}', a);
+ },
+ getFillColor: function (layer) {
+ let r = layer.get('fillColorR');
+ let g = layer.get('fillColorG');
+ let b = layer.get('fillColorB');
+ let a = layer.get('fillColorA');
+ return this.getRGBAToString(r, g, b, a);
+ },
+ getFontLineColor: function (layer) {
+ let r = layer.get('fontOutlineColorR');
+ let g = layer.get('fontOutlineColorG');
+ let b = layer.get('fontOutlineColorB');
+ let a = layer.get('fontOutlineColorA');
+ return this.getRGBAToString(r, g, b, a);
+ },
+ getFontFillColor: function (layer) {
+ let r = layer.get('fontColorR');
+ let g = layer.get('fontColorG');
+ let b = layer.get('fontColorB');
+ let a = layer.get('fontColorA');
+ return this.getRGBAToString(r, g, b, a);
+ },
+ point: function (layer, options, feature, resolution) {
+ let symbol = layer.get('iconNm');
+ let maticKey = layer.get('maticKey');
+ let maticVal1 = layer.get('maticVal1');
+ let image;
+ let style = new Style({
+ fill: new Fill(),
+ });
+
+ let text = this.getText(layer, feature, resolution);
+ style.setText(text);
+
+ if (symbol) {
+ let rotation = 0;
+ if (feature) {
+ let angDir = feature.get('angDir');
+ if (angDir) {
+ rotation = (angDir * Math.PI) / 180;
+ }
+ }
+ image = new Icon({
+ crossOrigin: 'anonymous',
+ scale: 1.2,
+ src: this.layericonPath + symbol,
+ rotation: rotation,
+ });
+ } else {
+ image = new Circle({
+ fill: new Fill({
+ color: '#4848B4',
+ }),
+ radius: 5,
+ });
+ }
+
+ const overlayHtml = feature.get('html');
+ if (overlayHtml) {
+ this.setOverlay(layer, feature, overlayHtml);
+ }
+
+ let baseResolution = 20;
+ let minScale = 0.3;
+ let maxScale = 1.5;
+ let scale = baseResolution / resolution > maxScale ? maxScale : baseResolution / resolution;
+ scale = Math.max(minScale, Math.min(maxScale, scale));
+ image.setScale(scale);
+ style.setImage(image);
+ _.extend(style, options);
+ return style;
+ },
+ line: function (layer, options, feature, resolution) {
+ let linewidth = layer.get('lnWdth');
+ let lineColor = layer.get('lnColor');
+ let style = new Style({
+ stroke: new Stroke({
+ color: lineColor,
+ width: linewidth,
+ }),
+ text: new Text(),
+ });
+
+ if (GisApp.Module.toolbar && GisApp.Module.toolbar.defaults.blockDiagramVisible) {
+ let blockDiagramLayerId = GisApp.Module.toolbar.getBlockDiagramLayerId(layer.get('lyrId'));
+ if (blockDiagramLayerId) {
+ style = this.getArrowStyle(feature, lineColor, linewidth);
+ }
+ }
+ return style;
+ },
+ poligon: function (layer, options, feature, resolution) {
+ const style = new Style();
+ const color = layer.get('polgnColor');
+ const fill = new Fill({
+ color,
+ });
+ style.setFill(fill);
+ _.extend(style, options);
+ return style;
+ },
+ lineLabelPoligon: function (layer, options, feature, resolution) {
+ let style = new Style();
+ const polgnColor = layer.get('polgnColor');
+ let fill = new Fill({
+ color: polgnColor,
+ });
+ let lnWdth = layer.get('lnWdth');
+ let lineColor = layer.get('lnColor');
+ let lineDash = layer.get('lineDash');
+ let stroke = new Stroke({
+ color: lineColor || '#000000',
+ width: lnWdth,
+ lineDash: lineDash ? [10, 5] : null,
+ });
+ let text = this.getText(layer, feature, resolution);
+ if (text) {
+ style.setText(text);
+ }
+ const overlayHtml = feature.get('html');
+ if (overlayHtml) {
+ this.setOverlay(layer, feature, overlayHtml);
+ }
+ style.setFill(fill);
+ style.setStroke(stroke);
+ _.extend(style, options);
+ return style;
+ },
+ imagePoligon: function (layer, options, feature, resolution) {
+ let style = new Style();
+ let symbol = layer.get('iconNm');
+ let retGeom = feature.getGeometry().getInteriorPoint();
+ let image = new Icon({
+ crossOrigin: 'anonymous',
+ imgSize: [20, 20],
+ src: this.layericonPath + symbol,
+ });
+
+ let scale = 0.5 / resolution > 1 ? 1 : 0.5 / resolution;
+ image.setScale(1);
+ style.setGeometry(retGeom);
+ style.setImage(image);
+
+ _.extend(style, options);
+ return style;
+ },
+ setOverlay: function (layer, feature, overlayHtml) {
+ const layerFeature = layer.getSource().getFeatures()[0];
+ const layerCoordinate = layerFeature ? layerFeature.getGeometry().getCoordinates() : null;
+
+ const properties = feature.getProperties();
+ const overLayId = feature?.get('overLayId');
+ const overLayMinZoom = layer.get('overLayMinZoom') || '12';
+ const overLayOffset = layer?.get('overLayOffset');
+ let findoOverlay = this.overlays[overLayId];
+ if (findoOverlay) {
+ const zoom = GisApp.Module.core.map.getView().getZoom();
+ if (Number(overLayMinZoom) < zoom) {
+ findoOverlay.options.element.style.display = 'block';
+ } else {
+ findoOverlay.options.element.style.display = 'none';
+ }
+ return findoOverlay;
+ }
+ let overlayContainer = document.createElement('div');
+ document.body.appendChild(overlayContainer);
+
+ if (typeof overlayHtml === 'string') {
+ //const sanitizedHtml = DOMPurify.sanitize(overlayHtml);
+ overlayContainer.innerHTML = sanitizedHtml;
+ }
+ // React 컴포넌트일 경우 createPortal() 사용
+ else if (React.isValidElement(overlayHtml)) {
+ ReactDOM.createRoot(overlayContainer).render(createPortal(overlayHtml, overlayContainer));
+ }
+
+ // 오버레이 생성
+ let overlay = new Overlay({
+ element: overlayContainer,
+ positioning: 'bottom-center',
+ stopEvent: false,
+ offset: overLayOffset ? overLayOffset : [0, 0], // 오프셋 설정 (위치 조정)
+ });
+ this.overlays[overLayId] = overlay;
+
+ // feature의 좌표 가져오기
+ let geometry = feature.getGeometry();
+ let coordinate;
+ // Point인 경우
+ if (geometry.getType() === 'Point') {
+ coordinate = geometry.getCoordinates();
+ }
+ // LineString 또는 MultiLineString인 경우
+ else if (geometry.getType() === 'LineString' || geometry.getType() === 'MultiLineString') {
+ coordinate = geometry.getFlatMidpoint(); // 선의 중간 좌표를 가져옴
+ }
+ // Polygon 또는 MultiPolygon인 경우
+ else if (geometry.getType() === 'Polygon' || geometry.getType() === 'MultiPolygon') {
+ coordinate = geometry.getExtent();
+ const center = [(coordinate[0] + coordinate[2]) / 2, (coordinate[1] + coordinate[3]) / 2];
+ coordinate = center;
+ } else {
+ console.error('Unsupported geometry type: ' + geometry.getType());
+ return;
+ }
+ overlay.setPosition(coordinate);
+ GisApp.Module.core.map.addOverlay(overlay);
+ const zoom = GisApp.Module.core.map.getView().getZoom();
+ if (Number(overLayMinZoom) < zoom) {
+ overlay.options.element.style.display = 'block';
+ } else {
+ overlay.options.element.style.display = 'none';
+ }
+ },
+ label: function (layer, options, feature, resolution) {
+ let textBaseline = layer.get('textBaseline') || 'bottom';
+ let textAlign = layer.get('textAlign') || 'center';
+ let fontExtnColor = layer.get('fontExtnColor');
+ let fontExtnWdth = Number(layer.get('fontExtnWdth') || '3.5');
+ let fontIntnColor = layer.get('fontIntnColor');
+ let style = new Style();
+ let prop = feature.getProperties();
+ let label = prop[layer.get('lblAtrbNm')];
+
+ let text = new Text({
+ stroke: new Stroke({
+ width: fontExtnWdth,
+ color: fontExtnColor,
+ }),
+ fill: new Fill({
+ color: fontIntnColor,
+ }),
+ font: layer.get('font'),
+ textBaseline: textBaseline,
+ textAlign: textAlign,
+ text: label,
+ });
+ style.setStroke(null);
+ style.setFill(null);
+ style.setText(text);
+ _.extend(style, options);
+
+ return style;
+ },
+ getText: function (layer, feature, resolution) {
+ let textBaseline = layer.get('textBaseline') || 'middle';
+ let textAlign = layer.get('textAlign') || 'center';
+ let fontExtnColor = layer.get('fontExtnColor');
+ let fontExtnWdth = Number(layer.get('fontExtnWdth') || '1');
+ let fontIntnColor = layer.get('fontIntnColor');
+ let minViewSize = layer.get('minViewSize');
+ if (feature == null) {
+ return;
+ }
+ let prop = feature.getProperties();
+ let label = prop[layer.get('lblAtrbNm')];
+ let scaleFactor = 1 / resolution; // 해상도가 낮아질수록 글자 크기 증가
+ const zoom = GisApp.Module.core.map.getView().getZoom();
+ if (zoom < minViewSize - 3) {
+ label = '';
+ }
+ let text = new Text({
+ stroke: new Stroke({
+ color: '#FFFFFF', // 흰색 테두리
+ width: fontExtnWdth,
+ }),
+ fill: new Fill({
+ color: fontIntnColor,
+ width: fontExtnWdth,
+ }),
+ font: `${layer.get('fontNm')}`, // 동적으로 폰트 크기 적용
+ textBaseline: textBaseline,
+ textAlign: textAlign,
+ overflow: true,
+ text: label,
+ offsetY: 20,
+ });
+
+ return text;
+ },
+ getArrowStyle: function (feature, lineColor, linewidth) {
+ let styles = [
+ new Style({
+ stroke: new Stroke({
+ color: lineColor,
+ width: linewidth,
+ }),
+ }),
+ ];
+ let geomLienString = GisApp.Module.core.getGeomLineOfFeature(feature);
+ if (geomLienString) {
+ let startCoord = geomLienString.getFirstCoordinate();
+ let endCoord = geomLienString.getLastCoordinate();
+
+ let dx = endCoord[0] - startCoord[0];
+ let dy = endCoord[1] - startCoord[1];
+ let rotation = Math.atan2(dy, dx);
+
+ let centerCoord = GisApp.Module.core.getCenterOfLineFeature(feature);
+ styles.push(
+ new Style({
+ geometry: new Point(centerCoord),
+ image: new RegularShape({
+ fill: new Fill({ color: lineColor }),
+ points: 3,
+ radius: 6,
+ rotation: -rotation,
+ angle: Math.PI / 2,
+ }),
+ })
+ );
+ }
+ return styles;
+ },
+ //레이어 스타일 반환
+ getLayerStyle: function (layer, options) {
+ let gobjTy = layer.get('gobjTy');
+ if (!gobjTy) return;
+ let style = this[gobjTy](layer, options);
+ return style;
+ },
+ getLayerStyleFunction: function (layer, options) {
+ let gobjTy = layer.get('gobjTy');
+ let styleFunction = function (feature, resolution) {
+ return this[gobjTy](layer, options, feature, resolution);
+ }.bind(this);
+ return styleFunction;
+ },
+ adjustOverlayPosition: function (overlay, coordinate) {
+ const overlays = this.overlays;
+ let adjustedCoordinate = [...coordinate]; // 초기 좌표 설정
+ const minDistance = 100000; // 최소 거리 (필요시 조정)
+ let offset = overlay.getOffset() || [0, 0];
+
+ let closestOverlay = null;
+ let closestDistance = Infinity;
+
+ // 모든 오버레이의 위치를 비교하여 가장 가까운 오버레이 찾기
+ Object.values(overlays).forEach(existingOverlay => {
+ if (existingOverlay) {
+ const existingCoordinate = existingOverlay.getPosition();
+ const existingOffset = existingOverlay.getOffset() || [0, 0];
+
+ // 현재 오버레이의 좌표 가져오기
+ const overlayCoordinate = overlay.getPosition();
+ if (!overlayCoordinate || !existingCoordinate) return;
+
+ // 거리 계산 (유클리드 거리)
+ const distance = Math.sqrt(Math.pow(overlayCoordinate[0] - existingCoordinate[0], 2) + Math.pow(overlayCoordinate[1] - existingCoordinate[1], 2));
+
+ // 가장 가까운 오버레이 업데이트
+ if (distance > 0 && distance < closestDistance) {
+ closestDistance = distance;
+ closestOverlay = existingOverlay;
+ } else {
+ offset = [50, 50];
+ }
+ }
+ });
+
+ // 가장 가까운 오버레이가 있으면 offset 조정
+ if (closestOverlay && closestDistance < minDistance) {
+ const closestOffset = closestOverlay.getOffset() || [0, 0];
+ // X, Y 좌표를 기존 offset 값의 반대로 설정
+ offset = [-closestOffset[0], -closestOffset[1]];
+ }
+
+ // 최종 offset 적용
+ overlay.setOffset(offset);
+ },
+};
+
+export default GisOlStyle;
diff --git a/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-boon.png b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-boon.png
new file mode 100644
index 000000000..f65ce99f2
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-boon.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-hab.png b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-hab.png
new file mode 100644
index 000000000..2ef70877e
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-hab.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon.png b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon.png
new file mode 100644
index 000000000..950edf246
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area.png
new file mode 100644
index 000000000..044562b55
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area_on.png
new file mode 100644
index 000000000..0129ff1bd
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next.png
new file mode 100644
index 000000000..075111dcf
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next_on.png
new file mode 100644
index 000000000..4fdbe3a00
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev.png
new file mode 100644
index 000000000..7e3fae7d2
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev_on.png
new file mode 100644
index 000000000..fb2d55cfd
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print.png
new file mode 100644
index 000000000..82596b83d
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print_on.png
new file mode 100644
index 000000000..bb7f27334
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler.png
new file mode 100644
index 000000000..501e460a4
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler_on.png
new file mode 100644
index 000000000..676a81bcc
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save.png
new file mode 100644
index 000000000..c057b9377
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save_on.png
new file mode 100644
index 000000000..bf8e6d623
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch.png
new file mode 100644
index 000000000..38a5f507f
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch_on.png
new file mode 100644
index 000000000..79fb54e03
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin.png
new file mode 100644
index 000000000..ac32fbab0
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin_on.png
new file mode 100644
index 000000000..f258a965f
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout.png
new file mode 100644
index 000000000..2ee0b5161
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout_on.png
new file mode 100644
index 000000000..58dcbfdae
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add.png
new file mode 100644
index 000000000..fe4f0993f
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add_on.png
new file mode 100644
index 000000000..37a836298
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del.png
new file mode 100644
index 000000000..3bb372ab9
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del_on.png
new file mode 100644
index 000000000..89e558739
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next.png
new file mode 100644
index 000000000..df4913c90
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next_on.png
new file mode 100644
index 000000000..4e5540b55
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev.png
new file mode 100644
index 000000000..e94bf6d42
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev_on.png
new file mode 100644
index 000000000..8645dedfa
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save.png
new file mode 100644
index 000000000..5f01dd55c
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save_on.png
new file mode 100644
index 000000000..b035687d6
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt.png
new file mode 100644
index 000000000..aaf2bbd08
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt_on.png
new file mode 100644
index 000000000..02d035a74
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap.png
new file mode 100644
index 000000000..fc91d4304
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap_on.png
new file mode 100644
index 000000000..f856d8fbe
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/ico_gis_sprite.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/ico_gis_sprite.png
new file mode 100644
index 000000000..3567cc6e4
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/ico_gis_sprite.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/icon-viewclose.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/icon-viewclose.png
new file mode 100644
index 000000000..d93ce9c05
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/icon-viewclose.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map1.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map1.png
new file mode 100644
index 000000000..7f96fcf5e
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map1.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map2.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map2.png
new file mode 100644
index 000000000..1a93f50e8
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map2.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map3.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map3.png
new file mode 100644
index 000000000..df80af6dc
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map3.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map4.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map4.png
new file mode 100644
index 000000000..02d015d12
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map4.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/loading.gif b/packages/common/business_components/src/ui/map/lib/images/toolbar/loading.gif
new file mode 100644
index 000000000..7c3683bb4
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/loading.gif differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/map_pin.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/map_pin.png
new file mode 100644
index 000000000..b6cc2f6aa
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/map_pin.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_off.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_off.png
new file mode 100644
index 000000000..d5dea9f67
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_off.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_on.png
new file mode 100644
index 000000000..9cb573875
Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_on.png differ
diff --git a/packages/common/business_components/src/ui/map/lib/util.js b/packages/common/business_components/src/ui/map/lib/util.js
new file mode 100644
index 000000000..9fe55869b
--- /dev/null
+++ b/packages/common/business_components/src/ui/map/lib/util.js
@@ -0,0 +1,596 @@
+
+//공통
+export const util = {
+ getOneToArrayByObjectKey: function (arr, key, value) {
+ let result = {};
+ arr.forEach(item => {
+ if (item[key] === value) {
+ result = item;
+ }
+ });
+ return result;
+ },
+ groupByProperty: (arr, prop, parentProp, group) => {
+ const tree = [];
+ const mappedArr = {};
+ let arrElem;
+ let mappedElem;
+ // 먼저 배열의 노드를 개체에 매핑하고 -> 해시 테이블을 생성
+ for (let i = 0; i < arr.length; i++) {
+ arrElem = arr[i];
+ if (group && arrElem.mnuGroup !== group) continue;
+ mappedArr[arrElem[prop]] = arrElem;
+ mappedArr[arrElem[prop]].children = [];
+ }
+
+ for (const mnuCd in mappedArr) {
+ if (mappedArr.hasOwnProperty(mnuCd)) {
+ mappedElem = mappedArr[mnuCd];
+ // 요소가 루트 수준에 없으면 자식의 부모 배열에 추가.
+ if (mappedElem[parentProp] && mappedArr[mappedElem[parentProp]]?.children) {
+ mappedArr[mappedElem[parentProp]].children.push(mappedElem);
+ }
+ // 요소가 루트 수준에 있는 경우 첫 번째 수준 요소 배열에 추가
+ else {
+ tree.push(mappedElem);
+ }
+ }
+ }
+ return tree;
+ },
+ getLowestChildren: function (obj) {
+ if (!obj.children || obj.children.length === 0) {
+ return obj;
+ } else {
+ return this.getLowestChildren(obj.children[0]);
+ }
+ },
+ hasClass: (e, clazz) => {
+ return e.target.classList.contains(clazz);
+ },
+ isNull: function (str) {
+ if (typeof str === 'undefined' || str === null || str === 'null' || str === '' || str === 'undefined') {
+ return true;
+ }
+ return false;
+ },
+ nvl: function (str, returnVal) {
+ if (this.isNull(str)) {
+ return returnVal;
+ }
+ return str;
+ },
+ isNumeric: function (str) {
+ const pattern = /\d/;
+ return pattern.test(str);
+ },
+ isAlpha: function (str) {
+ const pattern = /[a-zA-Z]/;
+ return pattern.test(str);
+ },
+ isAlphaNumeric: function (str) {
+ const pattern = /^[a-zA-Z0-9]+$/;
+ return pattern.test(str);
+ },
+ isSpecialChar: function (str) {
+ const pattern = /[~!@#$%^&*()_+|<>?:{}]/;
+ return pattern.test(str);
+ },
+ isKoreanAplhaNumeric: function (str) {
+ const pattern = /^[ㄱ-ㅎ|ㅏ-ㅣ|가-힣|a-zA-Z0-9]+$/;
+ return pattern.test(str);
+ },
+ isValidPassword: function (str) {
+ const pattern = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*(),.?":{}|<>])[a-zA-Z0-9!@#$%^&*(),.?":{}|<>]{8,20}$/;
+ return pattern.test(str);
+ },
+ isValidExt: function (fileName, allowedExtensions) {
+ const fileExtension = fileName.split('.').pop().toLowerCase();
+ return allowedExtensions.includes(fileExtension);
+ },
+ isValidAllDate: function (dateString) {
+ const formats = ['YYYY', 'YYYYMM', 'YYYYMMDD'];
+ for (let format of formats) {
+ if (moment(dateString, format, true).isValid()) {
+ return true;
+ }
+ }
+ return false;
+ },
+ isEmptyObject: function (param) {
+ return Object.keys(param).length === 0 && param.constructor === Object;
+ },
+ castArrayObjectToList: function (array, key) {
+ var newArray = [];
+ for (var i = 0; i < array.length; i++) {
+ newArray.push(array[i][key]);
+ }
+ return newArray;
+ },
+ arraysMatch: function (arr1, arr2) {
+ // 길이가 같은지 확인
+ if (arr1.length !== arr2.length) return false;
+
+ // 각 요소가 일치하는지 확인
+ return arr1.every((element, index) => element === arr2[index]);
+ },
+ //상위뎁스 객체들 조회
+ findObjectByKeyAndValue: function ({ array, find: { key: findKey, value: findValue }, result: { key: resultKey }, parents = [] }) {
+ for (let i = 0; i < array.length; i++) {
+ let obj = array[i];
+ let siblings = [...array];
+ //siblings.splice(i, 1); // 현재 메뉴를 제거하여 형제 메뉴들만 남김
+ let currentParents = [...parents, { [resultKey]: obj, siblings: siblings }];
+
+ if (obj[findKey] === findValue) {
+ let formattedResult = currentParents.reduce((acc, cur, idx) => {
+ acc[`${resultKey}${idx + 1}`] = { active: cur[resultKey], list: cur.siblings };
+ return acc;
+ }, {});
+ return formattedResult;
+ }
+ if (obj.children) {
+ let result = this.findObjectByKeyAndValue({ array: obj.children, find: { key: findKey, value: findValue }, result: { key: resultKey }, parents: currentParents });
+ if (result !== null) {
+ return result;
+ }
+ }
+ }
+ return null;
+ },
+ autoComma(val, returnVal) {
+ if (!val && val !== 0) return returnVal ? returnVal : '';
+
+ // 문자열로 변환하고, 숫자와 소수점만을 허용
+ val = String(val).replace(/[^0-9.-]/g, '');
+
+ // 소수점을 기준으로 숫자를 분리
+ let [integerPart, decimalPart] = val.split('.');
+
+ // 정수 부분에 대해 쉼표 추가
+ integerPart = integerPart.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
+
+ // 소수점 이하 숫자가 있다면 다시 합치기
+ return decimalPart ? `${integerPart}.${decimalPart}` : integerPart;
+ },
+ removeComma: function (str) {
+ str = String(str);
+ return str.replace(/[^\d]+/g, '');
+ },
+ removeSpaceAndTrim: function (str) {
+ if (str) {
+ str = String(str);
+ return str.replace(/\s/g, '');
+ } else {
+ return str;
+ }
+ },
+ isNotWhiteSpace: function (str) {
+ const hasWhitespace = /\s/.test(str);
+ return !hasWhitespace;
+ },
+ //숫자만 남기고 모두 제거해서 반환
+ retainOnlyNumbers: function (str) {
+ str = String(str);
+ return str.replace(/\D+/g, '');
+ },
+ proxyToObject: function (proxy) {
+ return JSON.parse(JSON.stringify(Object.assign({}, proxy)));
+ },
+ dateFormatter: function ({ input, isHan = false }) {
+ const value = this.retainOnlyNumbers(input);
+
+ if (value.length === 6) {
+ const date = moment(value, 'YYYYMM');
+ return date.format(isHan ? 'YYYY년 MM월' : 'YYYY-MM');
+ } else if (value.length === 8) {
+ const date = moment(value, 'YYYYMMDD');
+ return date.format(isHan ? 'YYYY년 MM월 DD일' : 'YYYY-MM-DD');
+ } else if (value.length === 14) {
+ const date = moment(value, 'YYYYMMDDHHmmss');
+ return date.format(isHan ? 'YYYY년 MM월 DD일 HH시 mm분 ss초' : 'YYYY-MM-DD HH:MM:SS');
+ } else {
+ return input;
+ }
+ },
+ generateYearArray: function (num) {
+ const currentYear = moment().year();
+ if (num <= 1 || isNaN(num)) {
+ return [(currentYear - 2).toString(), (currentYear - 1).toString(), currentYear.toString()];
+ }
+
+ const years = [];
+ for (let i = num - 1; i >= 0; i--) {
+ years.push((currentYear - i).toString());
+ }
+
+ return years;
+ },
+ getFileId: function () {
+ let timeStamp = moment().format('YYYYMMDDHHmmssSSS'); // YYYYMMDDHHmmssSSS 형태로 타임스탬프를 생성
+ return 'CFM_' + timeStamp.slice(-12); // slice(-12)는 문자열의 끝에서 12자리를 추출합니다.
+ },
+ lpad: function (number, pad, length) {
+ let str = number.toString();
+ while (str.length < length) {
+ str = pad + str;
+ }
+ return str;
+ },
+ convertKeysToCamelCase: function (obj) {
+ if (obj === null || typeof obj !== 'object') {
+ return obj;
+ }
+ return Object.keys(obj).reduce((acc, key) => {
+ const camelCaseKey = key.replace(/([-_]\w)/g, match => match.replace(/[-_]/, '').toUpperCase());
+ acc[camelCaseKey] = typeof obj[key] === 'object' ? this.convertKeysToCamelCase(obj[key]) : obj[key];
+ return acc;
+ }, {});
+ },
+ blobUrlToBlob: async blobUrl => {
+ try {
+ const response = await fetch(blobUrl);
+ return await response.blob();
+ } catch (error) {
+ console.error(error);
+ return new Blob();
+ }
+ },
+ hexToRgba: hex => {
+ const r = parseInt(hex.substring(1, 3), 16);
+ const g = parseInt(hex.substring(3, 5), 16);
+ const b = parseInt(hex.substring(5, 7), 16);
+ return `rgba(${r}, ${g}, ${b} , 0.5)`;
+ },
+ rgbaToHex: rgba => {
+ /*rgba값을 가져와서 rgb 로 추출 후 hex 값 반환 */
+ if (!rgba) return false;
+ const rgbArray = rgba.match(/\d+/g).slice(0, 3).map(Number);
+ return `#${((1 << 24) | (rgbArray[0] << 16) | (rgbArray[1] << 8) | rgbArray[2]).toString(16).slice(1).toUpperCase()}`;
+ },
+ rgbAddOpacity: (rgb, opacity) => {
+ const rgbValues = rgb.match(/\d+/g); // rgb 값을 배열로 추출
+ const colorWithOpacity = `rgba(${rgbValues[0]}, ${rgbValues[1]}, ${rgbValues[2]}, ${opacity})`; // rgba로 변환
+ return colorWithOpacity;
+ },
+ generateMonthArray: (pSmonth, pEmonth) => {
+ let startYear = Math.floor(pSmonth / 100);
+ let startMonth = pSmonth % 100;
+ let endYear = Math.floor(pEmonth / 100);
+ let endMonth = pEmonth % 100;
+
+ let months = [];
+
+ for (let year = startYear; year <= endYear; year++) {
+ let monthStart = year === startYear ? startMonth : 1;
+ let monthEnd = year === endYear ? endMonth : 12;
+
+ for (let month = monthStart; month <= monthEnd; month++) {
+ let monthString = month < 10 ? `0${month}` : `${month}`;
+ months.push(`${year}${monthString}`);
+ }
+ }
+
+ return months;
+ },
+ findDuplicatesForArr: (arr, key) => {
+ const map = new Map();
+ const duplicates = [];
+
+ arr.forEach(item => {
+ const keyValue = item[key];
+ if (map.has(keyValue)) {
+ if (!duplicates.includes(keyValue)) {
+ duplicates.push(keyValue);
+ }
+ } else {
+ map.set(keyValue, true);
+ }
+ });
+
+ return duplicates;
+ },
+ createHtmlFromJson: (html, data) => {
+ if (!html || util.isEmptyObject(html)) return null;
+ const { classNm, headerKey, rows } = JSON.parse(html);
+ if (!rows) return null;
+ const tableRows = rows
+ .map(row => {
+ const value = data?.[row.key] ?? null;
+ let displayValue;
+ if (typeof value === 'number') {
+ displayValue = util.autoComma(value, 0);
+ } else if (typeof value === 'string') {
+ displayValue = util.nvl(value, '-');
+ } else {
+ displayValue = '-';
+ }
+ return `
+
+ | ${row.th} |
+ ${displayValue} |
+
+ `;
+ })
+ .join('');
+ return `
+
+
+
+ |
+ ${data?.[headerKey] ?? '-'}
+ |
+
+ ${tableRows}
+
+
+ `;
+ },
+};
+
+/**
+ *
+ * 다이렉트로 import하여 사용하기 위해 export로 수정중
+ *
+ * */
+
+//객체 배열에서 특정 key의 value인 것을 추출
+export const getArrayByObjectKey = function (arr, key, value) {
+ return arr.filter(item => item[key] === value);
+};
+
+//소수점 1자리를 기준으로 점을 언더스코어로 변경
+export const numberDotToUnderscore = function (num) {
+ if (Number.isInteger(num)) {
+ return num.toString();
+ } else {
+ return num.toFixed(1).replace('.', '_');
+ }
+};
+
+export const stripHTML = function (htmlString) {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(htmlString, 'text/html');
+ return doc.body.textContent || '';
+};
+
+//URL에서 매개변수 추출
+export function getParameterByName(name, url = window.location.search) {
+ name = name.replace(/[\[\]]/g, '\\$&');
+ var regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)'),
+ results = regex.exec(url);
+ if (!results) return null;
+ if (!results[2]) return '';
+ return decodeURIComponent(results[2].replace(/\+/g, ' '));
+}
+
+export function dateFormatForDB({ date: isoString, format = 'YYYY-MM-DD HH:MI:SS' }) {
+ if (!isoString) return '';
+
+ let date = new Date(isoString);
+
+ let year = date.getFullYear();
+ let month = (date.getMonth() + 1).toString().padStart(2, '0');
+ let day = date.getDate().toString().padStart(2, '0');
+
+ let hours = date.getHours().toString().padStart(2, '0');
+ let minutes = date.getMinutes().toString().padStart(2, '0');
+ let seconds = date.getSeconds().toString().padStart(2, '0');
+
+ return (
+ format
+ //
+ .replace('YYYY', year)
+ .replace('MM', month)
+ .replace('DD', day)
+ .replace('HH', hours)
+ .replace('MI', minutes)
+ .replace('SS', seconds)
+ );
+}
+
+//blob 다운로드
+//blob 다운로드
+export function downloadFromBlob(res, data) {
+ const url = URL.createObjectURL(new Blob([res.data], { type: res.data.type }));
+ const link = document.createElement('a');
+
+ const contentDisposition = res.headers?.['content-disposition']; // 파일 이름
+ let fileName = data?.fileName || 'export'; //기본 파일 이름
+
+ if (contentDisposition) {
+ const [fileNameMatch] = contentDisposition.split(';').filter(str => str.includes('filename'));
+ if (fileNameMatch) {
+ [, fileName] = fileNameMatch.split('=');
+ // 파일명이 "UTF-8''" 등으로 인코딩되어 있을 수 있으니 decodeURIComponent 추가
+ fileName = decodeURIComponent(fileName);
+ }
+ }
+
+ link.href = url;
+ // 브라우저 인코딩 관련 이슈 방지를 위해 파일명에 대한 추가 decodeURI 처리
+ link.download = decodeURIComponent(fileName).replace(/"/g, '').trim();
+ link.style.display = 'none';
+ document.body.appendChild(link);
+ link.click();
+
+ link.remove();
+ URL.revokeObjectURL(url);
+}
+
+export function downloadBlobAsFile(blob, filename = 'export') {
+ // 브라우저에 따라 인코딩 처리 햔재는 파이어폭스만
+ const url = URL.createObjectURL(blob);
+ const encodedFilename = encodeURIComponent(filename);
+ const downloadLink = document.createElement('a');
+
+ downloadLink.href = url;
+ downloadLink.download = decodeURIComponent(encodedFilename);
+
+ document.body.appendChild(downloadLink);
+ downloadLink.click();
+
+ document.body.removeChild(downloadLink);
+ URL.revokeObjectURL(url);
+}
+
+export function viewFile(res, data) {
+ // Blob URL 생성
+ const url = URL.createObjectURL(new Blob([res.data], { type: res.data.type }));
+
+ let fileType = null;
+ let fileName = data?.fileName || 'export'; // 기본 파일 이름
+
+ // PDF 및 이미지 타입의 MIME 타입 확인
+ if (res.data.type === 'application/pdf') {
+ fileType = 'pdf';
+ } else if (res.data.type.startsWith('image/')) {
+ fileType = 'img';
+ } else if (res.data.type === 'application/octet-stream') {
+ // Content-Disposition 헤더에서 파일 이름 추출
+ const contentDisposition = res.headers?.['content-disposition'];
+ if (contentDisposition) {
+ const fileNameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
+ if (fileNameMatch && fileNameMatch[1]) {
+ fileName = fileNameMatch[1].replace(/['"]/g, ''); // 파일 이름 추출
+ fileName = decodeURIComponent(fileName); // URL 인코딩된 파일 이름을 디코딩
+ }
+ }
+
+ // 파일 확장자 추출
+ const fileExtension = fileName.split('.').pop().toLowerCase();
+
+ // 확장자를 통해 파일 타입 결정
+ if (fileExtension === 'pdf') {
+ fileType = 'pdf';
+ } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'].includes(fileExtension)) {
+ fileType = 'img';
+ } else {
+ console.error('지원하지 않는 파일 형식입니다.');
+ }
+ }
+
+ // URL과 파일 타입 반환
+ return { url, fileType, fileName };
+}
+
+export function getToday() {
+ return getDate('today');
+}
+
+export function getDate(type, diff) {
+ const date = new Date();
+
+ switch (type) {
+ case 'year':
+ date.setFullYear(date.getFullYear() + diff);
+ break;
+ case 'month':
+ date.setMonth(date.getMonth() + diff);
+ break;
+ case 'day':
+ date.setDate(date.getDate() + diff);
+ break;
+ case 'today':
+ date.setDate(date.getDate());
+ break;
+ default:
+ console.log('잘못된 타입입니다. 올바른 타입: 년, 월, 일');
+ return;
+ }
+
+ const year = date.getFullYear();
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
+ const day = date.getDate().toString().padStart(2, '0');
+
+ return `${year}${month}${day}`;
+}
+
+//promise 처리
+export const byPromise = function (actions) {
+ return actions.reduce((promise, action) => {
+ return promise.then(async () => {
+ if (typeof action === 'function') {
+ return await action();
+ } else {
+ return await action;
+ }
+ });
+ }, Promise.resolve());
+};
+
+export const getMessage = (msgGroup, msgKey, innerText, fix) => {
+ let resultString = GLOBAL.MESSAGE[msgGroup][msgKey];
+ const prefix = fix?.prefix || '';
+ const suffix = fix?.suffix || '';
+
+ if (innerText) {
+ for (const [key, value] of Object.entries(innerText)) {
+ const regex = new RegExp(`\\$\{${key}\}`, 'g');
+ resultString = resultString.replace(regex, value);
+ }
+ }
+
+ if (resultString) resultString = resultString.replace(/\\n/g, '\n');
+ return resultString ? `${prefix}${resultString}${suffix}` : '안내 메시지 로딩 오류 - 관리자에게 문의하세요.';
+};
+
+// global.js의 message를 읽어오는 alert
+export const gAlert = (msgGroup, msgKey, innerText, fix) => {
+ alert(getMessage(msgGroup, msgKey, innerText, fix));
+};
+
+// message를 confirm
+export const gConfirm = (msgGroup, msgKey, innerText, fix) => {
+ return window.confirm(getMessage(msgGroup, msgKey, innerText, fix));
+};
+
+// message를 함수 리턴
+export const gMessage = getMessage;
+
+// message를 save후 응답까지 처리
+export const gSaveCommonConfirm = async ({ message, api = async () => {}, param = {}, success, fail, prefix }) => {
+ try {
+ if (!gConfirm('CONFIRM', `${message}_QUEST`, null, { prefix })) return;
+
+ const result = await api(param, { isAlert: false });
+
+ if (result?.code !== 200) return;
+
+ const rep = message === 'UPLOAD' ? result.data.data : result.data;
+
+ if (rep > 0) {
+ gAlert('INFO', `${message}_COMPLETE`);
+ await success?.();
+ } else {
+ gAlert('ERROR', `${message}_FAIL`);
+ await fail?.();
+ }
+ } catch (error) {
+ console.error('오류 발생:', error); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력
+ }
+};
+
+export const nullToValue = (text, { value = '-', prefix = '', suffix = '' } = {}) => {
+ if (!text || text === value) {
+ return value;
+ }
+ return `${prefix}${text}${suffix}`;
+};
+
+export const filterArrayOfObjectsByKeys = (arrayOfObjects, keys) => {
+ return arrayOfObjects.map(obj => {
+ return keys.reduce((filteredObj, key) => {
+ if (obj.hasOwnProperty(key)) {
+ filteredObj[key] = obj[key];
+ }
+ return filteredObj;
+ }, {});
+ });
+};
+
+export const isMobile = () => {
+ const width = window.innerWidth > 0 ? window.innerWidth : window.screen.width;
+
+ return width < GLOBAL.MOBILE_SIZE;
+};